r75817 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r75816‎ | r75817 | r75818 >
Date:19:01, 1 November 2010
Author:questpc
Status:deferred (Comments)
Tags:
Comment:
Initial commit of WikiSync extension
Modified paths:
  • /trunk/extensions/WikiSync (added) (history)
  • /trunk/extensions/WikiSync/COPYING (added) (history)
  • /trunk/extensions/WikiSync/INSTALL (added) (history)
  • /trunk/extensions/WikiSync/README (added) (history)
  • /trunk/extensions/WikiSync/Snoopy (added) (history)
  • /trunk/extensions/WikiSync/Snoopy/AUTHORS (added) (history)
  • /trunk/extensions/WikiSync/Snoopy/COPYING.lib (added) (history)
  • /trunk/extensions/WikiSync/Snoopy/ChangeLog (added) (history)
  • /trunk/extensions/WikiSync/Snoopy/FAQ (added) (history)
  • /trunk/extensions/WikiSync/Snoopy/INSTALL (added) (history)
  • /trunk/extensions/WikiSync/Snoopy/NEWS (added) (history)
  • /trunk/extensions/WikiSync/Snoopy/README (added) (history)
  • /trunk/extensions/WikiSync/Snoopy/Snoopy.class.php (added) (history)
  • /trunk/extensions/WikiSync/Snoopy/TODO (added) (history)
  • /trunk/extensions/WikiSync/WikiSync.alias.php (added) (history)
  • /trunk/extensions/WikiSync/WikiSync.css (added) (history)
  • /trunk/extensions/WikiSync/WikiSync.js (added) (history)
  • /trunk/extensions/WikiSync/WikiSync.php (added) (history)
  • /trunk/extensions/WikiSync/WikiSyncApi.php (added) (history)
  • /trunk/extensions/WikiSync/WikiSyncBasic.php (added) (history)
  • /trunk/extensions/WikiSync/WikiSyncClient.php (added) (history)
  • /trunk/extensions/WikiSync/WikiSyncExporter.php (added) (history)
  • /trunk/extensions/WikiSync/WikiSyncPage.php (added) (history)
  • /trunk/extensions/WikiSync/WikiSync_i18n.php (added) (history)
  • /trunk/extensions/WikiSync/WikiSync_rtl.css (added) (history)
  • /trunk/extensions/WikiSync/WikiSync_utils.js (added) (history)
  • /trunk/extensions/WikiSync/pear (added) (history)
  • /trunk/extensions/WikiSync/pear/JSON.php (added) (history)
  • /trunk/extensions/WikiSync/pear/LICENSE (added) (history)

Diff [purge]

Index: trunk/extensions/WikiSync/WikiSyncExporter.php
@@ -0,0 +1,95 @@
 2+<?php
 3+/**
 4+ * ***** BEGIN LICENSE BLOCK *****
 5+ * This file is part of WikiSync.
 6+ *
 7+ * WikiSync 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+ * WikiSync 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
 18+ * along with WikiSync; if not, write to the Free Software
 19+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 20+ *
 21+ * ***** END LICENSE BLOCK *****
 22+ *
 23+ * WikiSync allows an AJAX-based synchronization of revisions and files between
 24+ * global wiki site and it's local mirror.
 25+ *
 26+ * To activate this extension :
 27+ * * Create a new directory named WikiSync into the directory "extensions" of MediaWiki.
 28+ * * Place the files from the extension archive there.
 29+ * * Add this line at the end of your LocalSettings.php file :
 30+ * require_once "$IP/extensions/WikiSync/WikiSync.php";
 31+ *
 32+ * @version 0.2.0
 33+ * @link http://www.mediawiki.org/wiki/Extension:WikiSync
 34+ * @author Dmitriy Sintsov <questpc@rambler.ru>
 35+ * @addtogroup Extensions
 36+ */
 37+
 38+if ( !defined( 'MEDIAWIKI' ) ) {
 39+ die( "This file is a part of MediaWiki extension.\n" );
 40+}
 41+
 42+class WikiSyncExporter extends WikiExporter {
 43+
 44+ function __construct( &$db ) {
 45+ parent::__construct( $db, WikiExporter::FULL );
 46+ }
 47+
 48+ /**
 49+ * include stylesheets and scripts; set javascript variables
 50+ * @param $dbres - database results (joined rows from revision, page and text tables)
 51+ * @return string xml dump of selected revisions
 52+ */
 53+ function dumpDBresult( $dbres ) {
 54+ // WikiExporter writes to stdout, so catch its
 55+ // output with an ob
 56+ ob_start();
 57+ $this->openStream();
 58+ $wrapper = $this->db->resultObject( $dbres );
 59+ # Output dump results
 60+ $this->outputPageStream( $wrapper );
 61+/*
 62+ if( $this->list_authors ) {
 63+ $this->outputPageStream( $wrapper );
 64+ }
 65+*/
 66+ $this->closeStream();
 67+ $exportxml = ob_get_contents();
 68+ ob_end_clean();
 69+ return $exportxml;
 70+ }
 71+
 72+} /* end of WikiSyncExporter class */
 73+
 74+class WikiSyncImportReporter extends ImportReporter {
 75+ private $mResultArr = array();
 76+
 77+ function reportPage( $title, $origTitle, $revisionCount, $successCount ) {
 78+ // Add a result entry
 79+ $r = array();
 80+ ApiQueryBase::addTitleInfo($r, $title);
 81+ $r['revisions'] = intval($successCount);
 82+ $this->mResultArr[] = $r;
 83+
 84+ # call the parent to do the logging
 85+ # avoid bug in 1.15.4 Special:Import (new file page text without the file uploaded)
 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 ) {
 88+ parent::reportPage( $title, $origTitle, $revisionCount, $successCount );
 89+ }
 90+ }
 91+
 92+ function getData() {
 93+ return $this->mResultArr;
 94+ }
 95+
 96+} /* end of WikiSyncImportReporter class */
Property changes on: trunk/extensions/WikiSync/WikiSyncExporter.php
___________________________________________________________________
Added: svn:eol-style
197 + native
Index: trunk/extensions/WikiSync/WikiSyncApi.php
@@ -0,0 +1,605 @@
 2+<?php
 3+/**
 4+ * ***** BEGIN LICENSE BLOCK *****
 5+ * This file is part of WikiSync.
 6+ *
 7+ * WikiSync 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+ * WikiSync 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
 18+ * along with WikiSync; if not, write to the Free Software
 19+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 20+ *
 21+ * ***** END LICENSE BLOCK *****
 22+ *
 23+ * WikiSync allows an AJAX-based synchronization of revisions and files between
 24+ * global wiki site and it's local mirror.
 25+ *
 26+ * To activate this extension :
 27+ * * Create a new directory named WikiSync into the directory "extensions" of MediaWiki.
 28+ * * Place the files from the extension archive there.
 29+ * * Add this line at the end of your LocalSettings.php file :
 30+ * require_once "$IP/extensions/WikiSync/WikiSync.php";
 31+ *
 32+ * @version 0.2.0
 33+ * @link http://www.mediawiki.org/wiki/Extension:WikiSync
 34+ * @author Dmitriy Sintsov <questpc@rambler.ru>
 35+ * @addtogroup Extensions
 36+ */
 37+
 38+if ( !defined( 'MEDIAWIKI' ) ) {
 39+ die( "This file is a part of MediaWiki extension.\n" );
 40+}
 41+
 42+/* ugly hack, required because 1.15.x API architecture is unsuitable to our needs */
 43+abstract class ApiWikiSync extends ApiQueryBase {
 44+
 45+ static $unsupported = 'ApiWikiSync does not support ';
 46+
 47+ // we construct like ApiBase, however we also use SQL select building methods from ApiQueryBase
 48+ public function __construct( $mainModule, $moduleName, $modulePrefix = '' ) {
 49+ // we call ApiBase only, ApiQueryBase is unsuitable to our needs
 50+ ApiBase::__construct( $mainModule, $moduleName, $modulePrefix = '' );
 51+ $this->mDb = null;
 52+ $this->resetQueryParams();
 53+ }
 54+
 55+ public function requestExtraData($pageSet) {
 56+ throw new MWException( self::$unsupported . __METHOD__ );
 57+ }
 58+
 59+ public function getQuery() {
 60+ throw new MWException( self::$unsupported . __METHOD__ );
 61+ }
 62+
 63+ protected function addPageSubItems($pageId, $data) {
 64+ throw new MWException( self::$unsupported . __METHOD__ );
 65+ }
 66+
 67+ protected function addPageSubItem($pageId, $item, $elemname = null) {
 68+ throw new MWException( self::$unsupported . __METHOD__ );
 69+ }
 70+
 71+ /**
 72+ * Gets a default slave database connection object
 73+ * @return Database
 74+ */
 75+ public function getDB() { // copypasted from ApiQuery class
 76+ if (!isset ($this->mSlaveDB)) {
 77+ $this->profileDBIn();
 78+ $this->mSlaveDB = wfGetDB(DB_SLAVE,'api');
 79+ $this->profileDBOut();
 80+ }
 81+ return $this->mSlaveDB;
 82+ }
 83+
 84+ public function selectNamedDB($name, $db, $groups) {
 85+ throw new MWException( self::$unsupported . __METHOD__ );
 86+ }
 87+
 88+ protected function getPageSet() {
 89+ throw new MWException( self::$unsupported . __METHOD__ );
 90+ }
 91+
 92+} /* end of WikiSyncQueryBuilder class */
 93+
 94+class ApiFindSimilarRev extends ApiWikiSync {
 95+
 96+ public function __construct( $main, $action ) {
 97+ parent :: __construct( $main, $action );
 98+ }
 99+
 100+ public function execute() {
 101+ $db = $this->getDB();
 102+ /* Get the parameters of the request. */
 103+ $params = $this->extractRequestParams();
 104+
 105+ $selectFields = array (
 106+ 'rev_id',
 107+ 'rev_page',
 108+ 'rev_timestamp',
 109+ 'rev_len',
 110+ 'rev_user_text',
 111+ 'OCTET_LENGTH( old_text ) AS text_len'
 112+ );
 113+ $this->addFields( $selectFields );
 114+ $this->addTables( array( 'revision', 'text' ) );
 115+ $this->addWhereRange( 'rev_id', $params['dir'], $params['startid'], $params['endid'] );
 116+ $this->addOption( 'LIMIT', $params['limit'] + 1 );
 117+ $this->addJoinConds(
 118+ array(
 119+ 'text' => array( 'INNER JOIN', 'rev_text_id=old_id' ),
 120+ )
 121+ );
 122+ $where = array();
 123+ if ( $params['timestamp'] !== null ) {
 124+ // wfTimestamp( TS_MW, $params['timestamp'] ) was done automatically by ApiBase
 125+ $where['rev_timestamp'] = $params['timestamp'];
 126+ }
 127+ if ( $params['usertext'] !== null ) {
 128+ $where['rev_user_text'] = $params['usertext'];
 129+ }
 130+ if ( count( $where ) > 0 ) {
 131+ $this->addWhere( $where );
 132+ }
 133+ $dbres = $this->select(__METHOD__);
 134+
 135+ $result = $this->getResult();
 136+ $limit = $params['limit'];
 137+
 138+ # return list of similar revisions
 139+ $count = 0;
 140+ while ( $row = $db->fetchObject( $dbres ) ) {
 141+ if ( ++$count > $limit ) {
 142+ $this->setContinueEnumParameter( 'startid', intval( $row->rev_id ) );
 143+ break;
 144+ }
 145+ $vals = $this->extractRowInfo( $row );
 146+ $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
 147+ if ( !$fit ) {
 148+ $this->setContinueEnumParameter( 'startid', intval( $row->rev_id ) );
 149+ break;
 150+ }
 151+ }
 152+ # place result list items into attributes of <similarrev> xml tag
 153+ $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'similarrev' );
 154+ $db->freeResult( $dbres );
 155+ }
 156+
 157+ private function extractRowInfo( $row ) {
 158+ $vals = array();
 159+ $vals['revid'] = $row->rev_id;
 160+ $vals['pageid'] = $row->rev_page;
 161+ $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->rev_timestamp );
 162+ $vals['revlen'] = $row->rev_len;
 163+ $vals['textlen'] = $row->text_len;
 164+ $vals['usertext'] = $row->rev_user_text;
 165+ return $vals;
 166+ }
 167+
 168+ public function getAllowedParams() {
 169+ return array(
 170+ 'startid' => array(
 171+ ApiBase :: PARAM_TYPE => 'integer'
 172+ ),
 173+ 'endid' => array(
 174+ ApiBase :: PARAM_TYPE => 'integer'
 175+ ),
 176+ 'dir' => array(
 177+ ApiBase :: PARAM_DFLT => 'older',
 178+ ApiBase :: PARAM_TYPE => array(
 179+ 'newer',
 180+ 'older'
 181+ )
 182+ ),
 183+ 'limit' => array (
 184+ ApiBase :: PARAM_DFLT => 10,
 185+ ApiBase :: PARAM_TYPE => 'limit',
 186+ ApiBase :: PARAM_MIN => 1,
 187+ ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
 188+ ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
 189+ ),
 190+ 'timestamp' => array(
 191+ ApiBase :: PARAM_TYPE => 'timestamp'
 192+ ),
 193+ 'usertext' => array(
 194+ ApiBase :: PARAM_TYPE => 'string'
 195+ )
 196+ );
 197+ }
 198+
 199+ public function getParamDescription() {
 200+ return array (
 201+ 'startid' => 'from which revision id to start enumeration (enum)',
 202+ 'endid' => 'stop revision enumeration on this revid (enum)',
 203+ 'dir' => 'direction of enumeration - towards "newer" or "older" revisions (enum)',
 204+ 'limit' => 'limit how many revisions will be returned (enum)',
 205+ 'timestamp' => 'Timestamp of revisions to look for',
 206+ 'usertext' => 'Name of user who created the revisions to look for'
 207+ );
 208+ }
 209+
 210+ public function getDescription() {
 211+ return 'Look for revisions with particular timestamp, text length and usertext';
 212+ }
 213+
 214+ protected function getExamples() {
 215+ return array (
 216+ 'Most recently created revisions',
 217+ 'api.php?action=similarrev',
 218+ 'The same as it would look in JSON (use without fm postfix)',
 219+ 'api.php?action=similarrev&format=jsonfm',
 220+ 'Get first results (with continuation) of revisions created by user Syntone',
 221+ 'api.php?action=similarrev&usertext=Syntone',
 222+ 'Get first results (with continuation) of revisions created at time 2008-03-03T20:29:24Z',
 223+ 'api.php?action=similarrev&timestamp=2008-03-03T20:29:24Z',
 224+ 'Get first results (with continuation) of revisions which has been created by user Syntone, at time 2010-02-24T08:07:21Z',
 225+ 'api.php?action=similarrev&usertext=Syntone&timestamp=2010-02-24T08:07:21Z',
 226+ );
 227+ }
 228+
 229+ public function getVersion() {
 230+ return __CLASS__;
 231+ }
 232+} /* end of ApiFindSimilarRev class */
 233+
 234+/**
 235+ * Enumerate all available page revisions by order of their creation (edit history)
 236+ *
 237+ * @ingroup API
 238+ */
 239+class ApiRevisionHistory extends ApiWikiSync {
 240+
 241+ var $xmlDumpMode = false;
 242+ var $rawOutputMode = false;
 243+
 244+ public function __construct( $main, $action ) {
 245+ parent :: __construct( $main, $action );
 246+ }
 247+
 248+ public function execute() {
 249+ $db = $this->getDB();
 250+ /* Get the parameters of the request. */
 251+ $params = $this->extractRequestParams();
 252+ # next line is required, because getCustomPrinter() is not being executed from FauxRequest
 253+ $this->xmlDumpMode = $params['xmldump'];
 254+
 255+ $selectFields = array (
 256+ 'rev_id',
 257+ 'rev_page',
 258+ 'rev_timestamp',
 259+ 'rev_len',
 260+ 'rev_user_text',
 261+ 'OCTET_LENGTH( old_text ) AS text_len'
 262+ );
 263+ $this->addFields( $selectFields );
 264+ $this->addTables( array( 'revision', 'text' ) );
 265+ $this->addWhereRange( 'rev_id', $params['dir'], $params['startid'], $params['endid'] );
 266+ $this->addOption( 'LIMIT', $params['limit'] + 1 );
 267+ $this->addJoinConds(
 268+ array(
 269+ 'text' => array( 'INNER JOIN', 'rev_text_id=old_id' )
 270+ )
 271+ );
 272+ $dbres = $this->select(__METHOD__);
 273+
 274+ $result = $this->getResult();
 275+ $limit = $params['limit'];
 276+
 277+ if ( $this->xmlDumpMode ) {
 278+ # use default IIS / Apache execution time limit which much larger than default PHP limit
 279+ set_time_limit( 300 );
 280+ $count = $db->numRows( $dbres );
 281+ $db->dataSeek( $dbres, $count - 1 );
 282+ $last_row = $db->fetchObject( $dbres );
 283+ $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 ???
 286+ $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' )
 288+ );
 289+ $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+ $dbres = $this->select(__METHOD__);
 298+ if ( !$this->rawOutputMode ) {
 299+ while ( $row = $db->fetchObject( $dbres ) ) {
 300+ $vals = $this->extractRowInfo( $row );
 301+ $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
 302+ }
 303+ $db->dataSeek( $dbres, 0 );
 304+ }
 305+ $exporter = new WikiSyncExporter( $db );
 306+ $exportxml = $exporter->dumpDBresult( $dbres );
 307+ if ( $this->rawOutputMode ) {
 308+ // Don't check the size of exported stuff
 309+ // It's not continuable, so it would cause more
 310+ // problems than it'd solve
 311+ $result->disableSizeCheck();
 312+ $result->reset();
 313+ // Raw formatter will handle this
 314+ $result->addValue(null, 'text', $exportxml);
 315+ $result->addValue(null, 'mime', 'text/xml');
 316+ $result->enableSizeCheck();
 317+ return;
 318+ }
 319+ $result->addValue( 'query', 'exportxml', $exportxml );
 320+ if ( $count > $limit ) {
 321+ $this->setContinueEnumParameter( 'startid', intval( $last_row->rev_id ) );
 322+ }
 323+ } else {
 324+ # revisions edit history mode
 325+ $count = 0;
 326+ while ( $row = $db->fetchObject( $dbres ) ) {
 327+ if ( ++$count > $limit ) {
 328+ $this->setContinueEnumParameter( 'startid', intval( $row->rev_id ) );
 329+ break;
 330+ }
 331+ $vals = $this->extractRowInfo( $row );
 332+ $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
 333+ if ( !$fit ) {
 334+ $this->setContinueEnumParameter( 'startid', intval( $row->rev_id ) );
 335+ break;
 336+ }
 337+ }
 338+ # place result list items into attributes of <revision> xml tag
 339+ $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'revision' );
 340+// $this->getResult()->setIndexedTagName( $resultData, 'page' );
 341+// $this->getResult()->addValue( null, $this->getModuleName(), $resultData );
 342+ $db->freeResult( $dbres );
 343+ }
 344+ }
 345+
 346+ private function extractRowInfo( $row ) {
 347+ $vals = array();
 348+ $vals['revid'] = $row->rev_id;
 349+ $vals['pageid'] = $row->rev_page;
 350+ $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->rev_timestamp );
 351+ $vals['revlen'] = $row->rev_len;
 352+ $vals['textlen'] = $row->text_len;
 353+ $vals['usertext'] = $row->rev_user_text;
 354+ if ( isset( $row->page_namespace ) ) {
 355+ $vals['namespace'] = $row->page_namespace;
 356+ }
 357+ if ( isset( $row->page_title ) ) {
 358+ $vals['title'] = $row->page_title;
 359+ }
 360+ if ( isset( $row->page_is_redirect ) ) {
 361+ $vals['redirect'] = $row->page_is_redirect;
 362+ }
 363+ return $vals;
 364+ }
 365+
 366+ public function getCustomPrinter() {
 367+ # If &xmldump and &rawxml are passed in request, use the raw formatter
 368+ if ( $this->xmlDumpMode = $this->getParameter( 'xmldump' ) ) {
 369+ # please note that this method is not called from FauxRequest
 370+ # so local API calls cannot be in rawxml mode
 371+ if ( $this->rawOutputMode = $this->getParameter( 'rawxml' ) ) {
 372+ return new ApiFormatRaw( $this->getMain(), $this->getMain()->createPrinterByName( 'xml' ) );
 373+ }
 374+ }
 375+ return null;
 376+ }
 377+
 378+ public function getAllowedParams() {
 379+ return array(
 380+ 'startid' => array(
 381+ ApiBase :: PARAM_TYPE => 'integer'
 382+ ),
 383+ 'endid' => array(
 384+ ApiBase :: PARAM_TYPE => 'integer'
 385+ ),
 386+ 'dir' => array(
 387+ ApiBase :: PARAM_DFLT => 'older',
 388+ ApiBase :: PARAM_TYPE => array(
 389+ 'newer',
 390+ 'older'
 391+ )
 392+ ),
 393+ 'limit' => array (
 394+ ApiBase :: PARAM_DFLT => 10,
 395+ ApiBase :: PARAM_TYPE => 'limit',
 396+ ApiBase :: PARAM_MIN => 1,
 397+ ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
 398+ ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
 399+ ),
 400+ 'xmldump' => false,
 401+ 'rawxml' => false
 402+ );
 403+ }
 404+
 405+ public function getParamDescription() {
 406+ return array (
 407+ 'startid' => 'from which revision id to start enumeration (enum)',
 408+ 'endid' => 'stop revision enumeration on this revid (enum)',
 409+ 'dir' => 'direction of enumeration - towards "newer" or "older" revisions (enum)',
 410+ 'limit' => 'limit how many revisions will be returned (enum)',
 411+ 'xmldump' => 'return xml dump of selected revisions instead',
 412+ 'rawxml' => 'return xml dump as raw xml'
 413+ );
 414+ }
 415+
 416+ public function getDescription() {
 417+ return 'Enumerate all available page revisions by order of their creation (edit history)';
 418+ }
 419+
 420+ protected function getExamples() {
 421+ return array (
 422+ 'Most recently created revisions',
 423+ 'api.php?action=revisionhistory',
 424+ 'The same as it would look in JSON (use without fm postfix)',
 425+ 'api.php?action=revisionhistory&format=jsonfm',
 426+ 'Get first results (with continuation) of revisions with id from 20000 to 19000',
 427+ 'api.php?action=revisionhistory&startid=20000&endid=19000',
 428+ 'Get first results (with continuation) of revisions with id values from 19000 to 20000 in reverse order',
 429+ 'api.php?action=revisionhistory&startid=19000&endid=20000&dir=newer',
 430+ 'Get xml dump of first results (with continuation) of revisions with id values from 19000 (wrap)',
 431+ 'api.php?action=revisionhistory&startid=19000&dir=newer&xmldump&format=jsonfm',
 432+ 'Get xml dump of first results (no continuation) of revisions with id values from 19000 (raw xml)',
 433+ 'api.php?action=revisionhistory&startid=19000&dir=newer&xmldump&rawxml',
 434+ 'Standard api xml export in nowrap mode (just for comparsion)',
 435+ 'api.php?action=query&export&exportnowrap'
 436+ );
 437+ }
 438+
 439+ public function getVersion() {
 440+ return __CLASS__;
 441+ }
 442+} /* end of ApiRevisionHistory class */
 443+
 444+class ApiGetFile extends ApiQueryBase {
 445+
 446+ var $dbkey = ''; // a dbkey name of file
 447+ var $fpath; // a filesystem path to file
 448+ var $block = ''; // empty block by default
 449+ var $stat; // file stats, false if error
 450+ var $offset = 0; // file offset
 451+
 452+ public function __construct( $main, $action ) {
 453+ parent :: __construct( $main, $action );
 454+ }
 455+
 456+ private function sendFile( $error_block = '' ) {
 457+ global $wgContLanguageCode;
 458+ if ( headers_sent() ) {
 459+ # there already was sent an error, no need to send anything
 460+ exit();
 461+ }
 462+ if ( $error_block !== '' ) {
 463+ # there was an error, send $error_block message instead of real block
 464+ $this->stat = false;
 465+ $this->block = $error_block;
 466+ $content_type = 'text/plain';
 467+ } else {
 468+ # indicates there is no error
 469+ $content_type = 'application/x-wiki';
 470+ }
 471+ $blocklen = strlen( $this->block );
 472+ # Cancel output buffering and gzipping if set
 473+ wfResetOutputBuffers();
 474+ if ( $this->stat !== false ) {
 475+ $partial_content = ($this->offset !== 0 || $this->stat['size'] !== $blocklen);
 476+ if ( $partial_content ) {
 477+ header( 'HTTP/1.1 206 Partial content' );
 478+ header( 'Accept-Ranges: bytes' );
 479+ }
 480+ header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', $this->stat['mtime'] ) . ' GMT' );
 481+ }
 482+ header( 'Cache-Control: no-cache' );
 483+ header( 'Content-type: ' . $content_type );
 484+ if ( $this->stat !== false ) {
 485+ header( "Content-Disposition: inline;filename*=utf-8'$wgContLanguageCode'" . urlencode( $this->dbkey ) );
 486+ if ( $partial_content ) {
 487+ header( "Content-Range: bytes " . $this->offset . "-" . ( $this->offset + $blocklen - 1 ) . "/" . $this->stat['size'] );
 488+ }
 489+ }
 490+ header( 'Content-Length: ' . $blocklen );
 491+ echo $this->block;
 492+ exit();
 493+ }
 494+
 495+ public function execute() {
 496+ /* Get the parameters of the request. */
 497+ $params = $this->extractRequestParams();
 498+ /*
 499+ if( is_null( $params['token'] ) ) {
 500+ $this->dieUsageMsg( array( 'missingparam', 'token' ) );
 501+ }
 502+ if( !$wgUser->matchEditToken( $params['token'] ) ) {
 503+ $this->dieUsageMsg( array( 'sessionfailure' ) );
 504+ }
 505+ */
 506+ if( !isset( $params['title'] ) ) {
 507+ $this->dieUsageMsg( array( 'missingparam', 'title' ) );
 508+ }
 509+ if( !isset( $params['timestamp'] ) ) {
 510+ $this->dieUsageMsg( array( 'missingparam', 'timestamp' ) );
 511+ }
 512+ if ( !$this->getMain()->canApiHighLimits() ) {
 513+ $this->dieUsageMsg( array( 'actionthrottledtext' ) );
 514+ }
 515+ $title = Title::newFromText( $params['title'], NS_FILE );
 516+ if ( $title instanceof Title ) {
 517+ $this->dbkey = $title->getDBkey();
 518+ } else {
 519+ $this->sendFile( 'Requested title ' . urlencode( $params['title'] ) . ' is invalid' );
 520+ }
 521+ $title = null;
 522+ $file = wfFindFile( $this->dbkey, $params['timestamp'] );
 523+ // only local files are supported, yet
 524+ if ( $file === false ||
 525+ !( $file instanceof LocalFile ) ||
 526+ !$file->exists() ) {
 527+ $this->sendFile( 'Requested file ' . urlencode( $params['title'] ) . ' does not exist or is not an instance of LocalFile' );
 528+ }
 529+ $file->lock();
 530+ if ( $file->isOld() ) {
 531+ $this->fpath = $file->getArchivePath( $file->getArchiveName() );
 532+ } else {
 533+ $this->fpath = $file->getPath();
 534+ }
 535+ if ( ( $f = @fopen( $this->fpath, 'rb' ) ) === false ) {
 536+ $file->unlock();
 537+ $this->sendFile( 'Cannot open file ' . urlencode( $this->fpath ) );
 538+ }
 539+ $this->stat = fstat( $f );
 540+ $this->offset = isset( $params['offset'] ) ? (int) $params['offset'] : 0;
 541+ if ( $this->offset < 0 ) {
 542+ $this->dieUsageMsg( array( 'missingparam', 'offset' ) );
 543+ }
 544+ if ( @fseek( $f, $this->offset ) !== 0 ) {
 545+ fclose( $f );
 546+ $file->unlock();
 547+ $this->sendFile( 'Cannot seek file ' . urlencode( $this->fpath ) );
 548+ }
 549+ $this->block = @fread( $f, $params['blocklen'] );
 550+ fclose( $f );
 551+ if ( $this->block === false ) {
 552+ $this->block = '';
 553+ fclose( $f );
 554+ $file->unlock();
 555+ $this->sendFile( 'Cannot read block ' . urlencode( $params['blocklen'] ) . ' from file ' . urlencode( $this->fpath ) );
 556+ }
 557+ // success
 558+ $file->unlock();
 559+ $this->sendFile();
 560+ }
 561+
 562+ public function getAllowedParams() {
 563+ return array(
 564+# 'token' => null,
 565+ 'title' => null,
 566+ 'timestamp' => array(
 567+ ApiBase :: PARAM_TYPE => 'timestamp'
 568+ ),
 569+ 'offset' => array(
 570+ ApiBase::PARAM_TYPE => 'integer'
 571+ ),
 572+ 'blocklen' => array(
 573+ ApiBase::PARAM_TYPE => 'integer',
 574+ ApiBase::PARAM_MIN => 1,
 575+ ApiBase::PARAM_MAX => 1024 * 1024,
 576+ ApiBase::PARAM_MAX2 => 2 * 1024 * 1024
 577+ )
 578+ );
 579+ }
 580+
 581+ public function getParamDescription() {
 582+ return array (
 583+# 'token' => 'Edit token. You can get one of these through prop=info',
 584+ 'title' => 'title of file to get',
 585+ 'timestamp' => 'timestamp of archived file (to make sure the file hasn\'t been changed while getting the chunks)',
 586+ 'offset' => 'start offset in file to get (default 0)',
 587+ 'blocklen' => 'length of block to get (omit to get all the file)'
 588+ );
 589+ }
 590+
 591+ public function getDescription() {
 592+ return 'Transfers file from remote wiki in chunks. Requires valid session id and login token. Always returns raw HTTP data. HTTP header \'Content-type: application/x-wiki\' indicates valid file chunk. Otherwise, if there is no such header, body contains error message.';
 593+ }
 594+
 595+ protected function getExamples() {
 596+ return array (
 597+ 'Get the chunk of new / archived file specified by timestamp',
 598+ 'api.php?action=getfile&format=json&title=File:Myfile.jpg&timestamp=2010-10-28T10:15:22Z&offset=512&blocklen=1024'
 599+ );
 600+ }
 601+
 602+ public function getVersion() {
 603+ return __CLASS__;
 604+ }
 605+
 606+} /* end of ApiGetFile class */
Property changes on: trunk/extensions/WikiSync/WikiSyncApi.php
___________________________________________________________________
Added: svn:eol-style
1607 + native
Index: trunk/extensions/WikiSync/WikiSyncPage.php
@@ -0,0 +1,236 @@
 2+<?php
 3+/**
 4+ * ***** BEGIN LICENSE BLOCK *****
 5+ * This file is part of WikiSync.
 6+ *
 7+ * WikiSync 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+ * WikiSync 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
 18+ * along with WikiSync; if not, write to the Free Software
 19+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 20+ *
 21+ * ***** END LICENSE BLOCK *****
 22+ *
 23+ * WikiSync allows an AJAX-based synchronization of revisions and files between
 24+ * global wiki site and it's local mirror.
 25+ *
 26+ * To activate this extension :
 27+ * * Create a new directory named WikiSync into the directory "extensions" of MediaWiki.
 28+ * * Place the files from the extension archive there.
 29+ * * Add this line at the end of your LocalSettings.php file :
 30+ * require_once "$IP/extensions/WikiSync/WikiSync.php";
 31+ *
 32+ * @version 0.2.0
 33+ * @link http://www.mediawiki.org/wiki/Extension:WikiSync
 34+ * @author Dmitriy Sintsov <questpc@rambler.ru>
 35+ * @addtogroup Extensions
 36+ */
 37+
 38+if ( !defined( 'MEDIAWIKI' ) ) {
 39+ die( "This file is a part of MediaWiki extension.\n" );
 40+}
 41+
 42+class WikiSyncPage extends SpecialPage {
 43+
 44+ var $sync_direction_tpl;
 45+ var $remote_login_form_tpl;
 46+ var $remote_log_tpl;
 47+ var $page_tpl;
 48+
 49+ function initRemoteLoginFormTpl() {
 50+ $remote_wiki_root = _QXML::specialchars( WikiSyncSetup::$remote_wiki_root );
 51+ $remote_wiki_user = _QXML::specialchars( WikiSyncSetup::$remote_wiki_user );
 52+ $js_remote_change = 'return WikiSync.remoteRootChange(this)';
 53+ $js_sync_files = 'return WikiSync.setSyncFiles(this);';
 54+ $this->remote_login_form_tpl =
 55+ array( '__tag'=>'table', 'class'=>'wikisync_remote_login',
 56+ array( '__tag'=>'form', 'id'=>'remote_login_form', 'onsubmit'=>'return WikiSync.submitRemoteLogin(this);',
 57+ array( '__tag'=>'tr',
 58+ array( '__tag'=>'th', 'colspan'=>'2', 'style'=>'text-align:center; ', wfMsgHtml( 'wikisync_login_to_remote_wiki' ) )
 59+ ),
 60+ array( '__tag'=>'tr', 'title'=>wfMsgHtml( 'wikisync_remote_wiki_example' ),
 61+ array( '__tag'=>'td', wfMsgHtml( 'wikisync_remote_wiki_root' ) ),
 62+ array( '__tag'=>'td', array( '__tag'=>'input', 'type'=>'text', 'name'=>'remote_wiki_root' , 'value'=>$remote_wiki_root, 'onkeyup'=>$js_remote_change, 'onchange'=>$js_remote_change ) )
 63+ ),
 64+ array( '__tag'=>'tr',
 65+ array( '__tag'=>'td', wfMsgHtml( 'wikisync_remote_wiki_user' ) ),
 66+ array( '__tag'=>'td', array( '__tag'=>'input', 'type'=>'text', 'name'=>'remote_wiki_user', 'value'=>$remote_wiki_user ) )
 67+ ),
 68+ array( '__tag'=>'tr',
 69+ array( '__tag'=>'td', wfMsgHtml( 'wikisync_remote_wiki_pass' ) ),
 70+ array( '__tag'=>'td', array( '__tag'=>'input', 'type'=>'password', 'name'=>'remote_wiki_pass' ) )
 71+ ),
 72+ array( '__tag'=>'tr',
 73+ array( '__tag'=>'td', 'colspan'=>'2', wfMsgHtml( 'wikisync_sync_files' ), array( '__tag'=>'input', 'type'=>'checkbox', 'id'=>'ws_sync_files', 'name'=>'ws_sync_files', 'onchange'=>$js_sync_files, 'onmouseup'=>$js_sync_files, 'checked'=>'' ) )
 74+ ),
 75+ array( '__tag'=>'tr',
 76+ array( '__tag'=>'td', array( '__tag'=>'input', 'id'=>'wikisync_synchronization_button', 'type'=>'button', 'value'=>wfMsgHtml( 'wikisync_synchronization_button' ), 'disabled'=>'', 'onclick'=>'return WikiSync.process(\'init\')' ) ),
 77+ array( '__tag'=>'td', 'style'=>'text-align:right; ', array( '__tag'=>'input', 'id'=>'wikisync_submit_button', 'type'=>'submit', 'value'=>wfMsgHtml( 'wikisync_remote_login_button' ) ) )
 78+ )
 79+ )
 80+ );
 81+ }
 82+
 83+ function initRemoteLogTpl() {
 84+ $this->remote_log_tpl =
 85+ array( '__tag'=>'table', 'class'=>'wikisync_remote_log',
 86+ array( '__tag'=>'tr',
 87+ array( '__tag'=>'th', 'style'=>'text-align:center; ', wfMsgHtml( 'wikisync_remote_log' ) )
 88+ ),
 89+ array( '__tag'=>'tr',
 90+ array( '__tag'=>'td',
 91+ array( '__tag'=>'div', 'id'=>'wikisync_remote_log' )
 92+ )
 93+ ),
 94+ array( '__tag'=>'tr',
 95+ array( '__tag'=>'td',
 96+ array( '__tag'=>'input', 'type'=>'button', 'value'=>wfMsgHtml( 'wikisync_clear_log' ), 'onclick'=>'return WikiSync.clearLog()' )
 97+ )
 98+ )
 99+ );
 100+ }
 101+
 102+ function initSyncDirectionTpl() {
 103+ global $wgServer, $wgScriptPath;
 104+ $this->sync_direction_tpl =
 105+ array(
 106+ array( '__tag'=>'div', 'style'=>'width:100%; font-weight:bold; text-align:center; ', wfMsgHTML( 'wikisync_direction' ) ),
 107+ array( '__tag'=>'table', 'style'=>'margin:0 auto 0 auto; ',
 108+ array( '__tag'=>'tr',
 109+ array( '__tag'=>'td', 'style'=>'text-align:right; ', wfMsgHTML( 'wikisync_local_root' ) ),
 110+ array( '__tag'=>'td', 'rowspan'=>'2', 'style'=>'vertical-align:middle; ', array( '__tag'=>'input', 'id'=>'wikisync_direction_button', 'type'=>'button', 'value'=>'&lt;=', 'onclick'=>'return WikiSync.setDirection(this)' ) ),
 111+ array( '__tag'=>'td', wfMsgHTML( 'wikisync_remote_root' ) )
 112+ ),
 113+ array( '__tag'=>'tr',
 114+ array( '__tag'=>'td', 'style'=>'text-align:right; ', $wgServer . $wgScriptPath ),
 115+ array( '__tag'=>'td', 'id'=>'wikisync_remote_root', WikiSyncSetup::$remote_wiki_root )
 116+ )
 117+ )
 118+ );
 119+ }
 120+
 121+ function initPercentsIndicatorTpl( $id ) {
 122+ return
 123+ array( '__tag'=>'table', 'id'=>$id, 'class'=>'wikisync_percents_indicator', 'style'=>'display: none;',
 124+ array( '__tag'=>'tr',
 125+ // progress explanation hint
 126+ array( '__tag'=>'td', 'style'=>'font-size:9pt; ', 'colspan'=>'2', '' )
 127+ ),
 128+ array( '__tag'=>'tr', 'style'=>'border:1px solid gray; ',
 129+ array( '__tag'=>'td', 'style'=>'width:0%; background-color:Gold; display: none; ', '' ),
 130+ array( '__tag'=>'td', 'style'=>'width:100%;', '' )
 131+ )
 132+ );
 133+ }
 134+
 135+ function initPageTpl() {
 136+ $this->page_tpl =
 137+ array( '__tag'=>'table',
 138+ array( '__tag'=>'tr',
 139+ array( '__tag'=>'td', 'colspan'=>'2', &$this->sync_direction_tpl )
 140+ ),
 141+ array( '__tag'=>'tr',
 142+ array( '__tag'=>'td', 'style'=>'width:50%; ', &$this->remote_log_tpl ),
 143+ array( '__tag'=>'td', 'style'=>'width:50%; ', &$this->remote_login_form_tpl )
 144+ ),
 145+ array( '__tag'=>'tr',
 146+ array( '__tag'=>'td', 'colspan'=>'2',
 147+ $this->initPercentsIndicatorTpl( 'wikisync_xml_percents' ),
 148+ $this->initPercentsIndicatorTpl( 'wikisync_files_percents' )
 149+ )
 150+ ),
 151+ array( '__tag'=>'tr',
 152+ array( '__tag'=>'td', 'colspan'=>'2', 'id'=>'wikisync_iframe_location' , '' )
 153+ ),
 154+ array( '__tag'=>'tr',
 155+ array( '__tag'=>'td', 'colspan'=>'2',
 156+ array( '__tag'=> 'iframe', 'id'=>'wikisync_iframe', 'style' => 'width:100%; height:200px; display:none; ' )
 157+ )
 158+ )
 159+ );
 160+ }
 161+
 162+ /*
 163+ * include stylesheets and scripts; set javascript variables
 164+ * @param $outputPage - an instance of OutputPage
 165+ * @param $isRTL - whether the current language is RTL
 166+ */
 167+ static function headScripts( &$outputPage, $isRTL ) {
 168+ global $wgJsMimeType;
 169+ $outputPage->addLink(
 170+ array( 'rel' => 'stylesheet', 'type' => 'text/css', 'href' => WikiSyncSetup::$ScriptPath . '/WikiSync.css?' . WikiSyncSetup::$version )
 171+ );
 172+ if ( $isRTL ) {
 173+ $outputPage->addLink(
 174+ array( 'rel' => 'stylesheet', 'type' => 'text/css', 'href' => WikiSyncSetup::$ScriptPath . '/WikiSync_rtl.css?' . WikiSyncSetup::$version )
 175+ );
 176+ }
 177+ $outputPage->addScript(
 178+ '<script type="' . $wgJsMimeType . '" src="' . WikiSyncSetup::$ScriptPath . '/WikiSync.js?' . WikiSyncSetup::$version . '"></script>
 179+ <script type="' . $wgJsMimeType . '" src="' . WikiSyncSetup::$ScriptPath . '/WikiSync_Utils.js?' . WikiSyncSetup::$version . '"></script>
 180+ <script type="' . $wgJsMimeType . '">
 181+ WikiSync.setLocalNames( ' .
 182+ self::getJsObject( 'wsLocalMessages', 'last_op_error', 'synchronization_confirmation', 'synchronization_success', 'already_synchronized', 'sync_to_itself', 'diff_search', 'revision', 'file_size_mismatch' ) .
 183+ ');</script>' . "\n"
 184+ );
 185+ }
 186+
 187+ static function getJsObject( $method_name ) {
 188+ $args = func_get_args();
 189+ array_shift( $args ); // remove $method_name from $args
 190+ $result = '{ ';
 191+ $firstElem = true;
 192+ foreach ( $args as &$arg ) {
 193+ if ( $firstElem ) {
 194+ $firstElem = false;
 195+ } else {
 196+ $result .= ', ';
 197+ }
 198+ $result .= $arg . ': "' . Xml::escapeJsString( call_user_func( array( 'self', $method_name ), $arg ) ) . '"';
 199+ }
 200+ $result .= ' }';
 201+ return $result;
 202+ }
 203+
 204+ /*
 205+ * currently passed to Javascript:
 206+ * localMessages
 207+ */
 208+ /*
 209+ * getJsObject callback
 210+ */
 211+ static private function wsLocalMessages( $arg ) {
 212+ return wfMsg( "wikisync_js_${arg}" );
 213+ }
 214+
 215+ function __construct() {
 216+ parent::__construct( 'WikiSync', 'delete' );
 217+ WikiSyncSetup::initUser();
 218+ }
 219+
 220+ function execute( $param ) {
 221+ global $wgOut, $wgContLang;
 222+ global $wgUser;
 223+ if ( !$wgUser->isAllowed( 'delete' ) ) {
 224+ $wgOut->permissionRequired('delete');
 225+ return;
 226+ }
 227+
 228+ self::headScripts( $wgOut, $wgContLang->isRTL() );
 229+ $wgOut->setPagetitle( wfMsgHtml( 'wikisync' ) );
 230+ $this->initSyncDirectionTpl();
 231+ $this->initRemoteLoginFormTpl();
 232+ $this->initRemoteLogTpl();
 233+ $this->initPageTpl();
 234+ $wgOut->addHTML( _QXML::toText( $this->page_tpl ) );
 235+ }
 236+
 237+} /* end of WikiSyncPage class */
Property changes on: trunk/extensions/WikiSync/WikiSyncPage.php
___________________________________________________________________
Added: svn:eol-style
1238 + native
Index: trunk/extensions/WikiSync/WikiSyncClient.php
@@ -0,0 +1,882 @@
 2+<?php
 3+/**
 4+ * ***** BEGIN LICENSE BLOCK *****
 5+ * This file is part of WikiSync.
 6+ *
 7+ * WikiSync 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+ * WikiSync 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
 18+ * along with WikiSync; if not, write to the Free Software
 19+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 20+ *
 21+ * ***** END LICENSE BLOCK *****
 22+ *
 23+ * WikiSync allows an AJAX-based synchronization of revisions and files between
 24+ * global wiki site and it's local mirror.
 25+ *
 26+ * To activate this extension :
 27+ * * Create a new directory named WikiSync into the directory "extensions" of MediaWiki.
 28+ * * Place the files from the extension archive there.
 29+ * * Add this line at the end of your LocalSettings.php file :
 30+ * require_once "$IP/extensions/WikiSync/WikiSync.php";
 31+ *
 32+ * @version 0.2.0
 33+ * @link http://www.mediawiki.org/wiki/Extension:WikiSync
 34+ * @author Dmitriy Sintsov <questpc@rambler.ru>
 35+ * @addtogroup Extensions
 36+ */
 37+
 38+if ( !defined( 'MEDIAWIKI' ) ) {
 39+ die( "This file is a part of MediaWiki extension.\n" );
 40+}
 41+
 42+/* todo: use HttpRequest or curl instead */
 43+class WikiSnoopy extends Snoopy {
 44+
 45+ var $api_url;
 46+ var $index_url;
 47+ var $cookie_prefix = null;
 48+ var $sessionid;
 49+ var $logintoken;
 50+
 51+ function __construct() {
 52+ $this->passcookies = true;
 53+ if ( WikiSyncSetup::$proxy_host !== '' ) { $this->proxy_host = WikiSyncSetup::$proxy_host; }
 54+ if ( WikiSyncSetup::$proxy_port !== '' ) { $this->proxy_port = WikiSyncSetup::$proxy_port; }
 55+ if ( WikiSyncSetup::$proxy_user !== '' ) { $this->proxy_user = WikiSyncSetup::$proxy_user; }
 56+ if ( WikiSyncSetup::$proxy_pass !== '' ) { $this->proxy_pass = WikiSyncSetup::$proxy_pass; }
 57+ }
 58+
 59+ /**
 60+ * set remote context provided to simplify further access
 61+ * @param $rc - remote context (url of remote api and cookies to send) in PHP stdClass
 62+ * ( 'wikiroot'->val, 'userid'->val, 'username'->val, 'logintoken'->val, 'cookieprefix'->val, 'sessionid'->val )
 63+ * only wikiroot is absoultely necessary, another parameters are optional
 64+ */
 65+ function setContext( $rc ) {
 66+ if ( is_string( $rc ) ) {
 67+ $rc = json_decode( $rc, true );
 68+ }
 69+ if ( !isset( $rc['wikiroot'] ) ) {
 70+ throw new MWException( 'wikiroot is undefined in ' . __METHOD__ );
 71+ }
 72+ if ( substr( $rc['wikiroot'], 0, 4 ) !== "http" ) {
 73+ throw new MWException( 'api_url has unsupported schema (' . $this->api_url . ') in ' . __METHOD__ );
 74+ }
 75+ $this->api_url = $rc['wikiroot'] . '/api.php';
 76+ $this->index_url = $rc['wikiroot'] . '/index.php';
 77+ # optionally construct session cookies
 78+ if ( isset( $rc['cookieprefix'] ) ) {
 79+ $this->cookie_prefix = $rc['cookieprefix'];
 80+ $this->sessionid = $rc['sessionid'];
 81+ $this->logintoken = $rc['logintoken'];
 82+ $this->setCookie( '_session', $this->sessionid );
 83+ $this->setCookie( 'UserName', $rc['username'] );
 84+ $this->setCookie( 'UserID', $rc['userid'] );
 85+ $this->setCookie( 'Token', $this->logintoken );
 86+ }
 87+ }
 88+
 89+ function setCookie( $name, $val ) {
 90+ if ( $this->cookie_prefix === null ) {
 91+ throw new MWException( 'cookie_prefix was previousely not set in ' . __METHOD__ );
 92+ }
 93+ $this->cookies[$this->cookie_prefix . $name] = $val;
 94+ }
 95+
 96+ function submitToApi( array $formvars, $formfiles = '' ) {
 97+ $this->submit( $this->api_url, $formvars, $formfiles );
 98+ }
 99+
 100+} /* end of WikiSnoopy class */
 101+
 102+class WikiSyncJSONresult {
 103+
 104+ var $encodeResult = true;
 105+ var $jr = array(); // JSON result
 106+
 107+ /**
 108+ * @param $encodeResult - boolean
 109+ * true : JSON result encoded as string (default)
 110+ * false : JSON result in PHP associative array
 111+ * @param $status '1' - success (jr is valid and usable), '0' - failure
 112+ * @param $code - string code of result
 113+ * @param $msg - localized explanation of code
 114+ */
 115+ function __construct( $encodeResult = true, $status = '0', $code = null, $msg = null ) {
 116+ $this->setEncodeResult( $encodeResult );
 117+ $this->setStatus( $status );
 118+ $this->setCode( $code, $msg );
 119+ }
 120+
 121+ function setEncodeResult( $value ) {
 122+ $this->encodeResult = (boolean) $value;
 123+ }
 124+
 125+ function setStatus( $val ) {
 126+ $this->jr['ws_status'] = $val;
 127+ }
 128+
 129+ function get( $key ) {
 130+ return $this->jr["ws_${key}"];
 131+ }
 132+
 133+ function set( $key, $val ) {
 134+ $this->jr["ws_${key}"] = $val;
 135+ }
 136+
 137+ /**
 138+ * @param $code - optionally set code value AND msg value by code value
 139+ * @param $msg - optionally overrides default msg value
 140+ */
 141+ function setCode( $code = null, $msg = null ) {
 142+ if ( $code !== null ) {
 143+ $this->jr['ws_code'] = $code;
 144+ } else {
 145+ if ( isset( $this->jr['ws_code'] ) ) {
 146+ $code = $this->jr['ws_code'];
 147+ }
 148+ }
 149+ if ( $msg === null ) {
 150+ if ( $code !== null ) {
 151+ # do not overwrite previousely set message, if any
 152+ if ( !isset( $this->jr['ws_msg'] ) ) {
 153+ $this->jr['ws_msg'] = wfMsg( "wikisync_api_result_${code}" );
 154+ }
 155+ }
 156+ } else {
 157+ $this->jr['ws_msg'] = $msg;
 158+ }
 159+# $this->jr['ws_msg'] = (($msg === null) ? (($code !==null) ? wfMsg( "wikisync_api_result_${code}" ) : '' ) : $msg);
 160+ }
 161+
 162+ /**
 163+ * appends JSON result from API to our JSON result
 164+ * @param $json_result JSON result we got from API call
 165+ */
 166+ function append( array $json_result ) {
 167+ # check for key conflicts
 168+ if ( count( array_intersect_key( $this->jr, $json_result ) ) > 0 ) {
 169+ throw new MWException( 'JSON keys conflict in ' . __METHOD__ );
 170+ }
 171+ $this->jr = array_merge( $this->jr, $json_result );
 172+ }
 173+
 174+ function getResult( $code = null, $msg = null ) {
 175+ $this->setCode( $code, $msg );
 176+ if ( $this->encodeResult ) {
 177+ return json_encode( $this->jr );
 178+ } else {
 179+ return $this->jr;
 180+ }
 181+ }
 182+
 183+} /* end of WikiSyncJSONresult class */
 184+
 185+class WikiSyncClient {
 186+
 187+ const RESULT_JSON_STRING = 0;
 188+ const RESULT_JSON_ARRAY = 1;
 189+ const RESULT_SNOOPY = 2;
 190+
 191+ # direction of xml synchronization:
 192+ # true - from remote wiki to local wiki, false - from local wiki to remote wiki
 193+ static $directionToLocal;
 194+ # remote context as encoded JSON string
 195+ static $remoteContextJSON;
 196+ # client's WikiSyncJSONresult instance to return
 197+ static $json_result;
 198+ # client "mini-api" parameters for validation
 199+ static $client_params;
 200+
 201+ static $parameters_validator = array(
 202+ 'transferFileBlock' => array(
 203+ 'title' => 'string',
 204+ 'timestamp' => 'timestamp',
 205+ 'offset' => 'int',
 206+ 'blocklen' => 'int'
 207+ ),
 208+ 'syncXMLchunk' => array(
 209+ 'startid' => 'int',
 210+ 'limit' => 'int',
 211+ 'dst_import_token' => 'string'
 212+ ),
 213+ 'findNewFiles' => array(
 214+ 'chunk_files' => 'array'
 215+ ),
 216+ 'uploadLocalFile' => array(
 217+ 'file_name' => 'string',
 218+ 'file_size' => 'int',
 219+ 'file_timestamp' => 'timestamp'
 220+ )
 221+ );
 222+
 223+ /**
 224+ * WikiSync own client parameters check (a kind of mini-api)
 225+ * @param $client_key - method key to have the parameters to be validated against
 226+ */
 227+ static function checkClientParameters( $client_key ) {
 228+ if ( !is_array( self::$client_params ) ) {
 229+ return "Submitted parameters (" . self::$client_params . ") are not valid JSON";
 230+ }
 231+ foreach ( self::$parameters_validator[$client_key] as $key => $type ) {
 232+ if ( !isset( self::$client_params[$key] ) ) {
 233+ return "Parameter $key is undefined in $client_key(), should have type $type";
 234+ }
 235+ $value = self::$client_params[$key];
 236+ $error = "Parameter $key has value ($value) should have type $type";
 237+ switch ( $type ) {
 238+ case 'bool' :
 239+ if ( !is_bool( $value ) ) { return $error; }
 240+ break;
 241+ case 'string' :
 242+ if ( !is_string( $value ) ) { return $error; }
 243+ break;
 244+ case 'array' :
 245+ if ( !is_array( $value ) ) { return $error; }
 246+ break;
 247+ case 'timestamp' :
 248+ if ( wfTimestamp( TS_UNIX, $value ) === 0 ) { return $error; }
 249+ break;
 250+ case 'int' :
 251+ if ( !is_int( $value ) ) { return $error; }
 252+ break;
 253+ case 'int_string' :
 254+ if ( intval( $value ) != $value ) { return $error; }
 255+ break;
 256+ case 'user' :
 257+ if ( is_null( Title::makeTitleSafe( NS_USER, $value ) ) ) { return $error; }
 258+ break;
 259+ }
 260+ }
 261+ return true;
 262+ }
 263+
 264+ /*
 265+ * called via AJAX to perform remote login via the API
 266+ * @param $args[0] : remote wiki root
 267+ * @param $args[1] : remote wiki user
 268+ * @param $args[2] : remote wiki password
 269+ * @return JSON result of second phase login (token confirmed, used logged in) from the remote API
 270+ */
 271+ static function remoteLogin() {
 272+ /*
 273+ * '_status' success means that both HTTP request and API request were successful
 274+ * '_code' is provided as an reason for 'status'
 275+ * '_msg' is almost always provided to display in JS log (error, success)
 276+ */
 277+ $args = func_get_args();
 278+ $json_result = new WikiSyncJSONresult();
 279+ if ( !WikiSyncSetup::initUser() ) {
 280+ # not enough priviledges to run this method
 281+ return $json_result->getResult( 'noaccess' );
 282+ }
 283+ $snoopy = new WikiSnoopy();
 284+ list( $remote_wiki_root, $remote_wiki_user, $remote_wiki_password ) = $args;
 285+ $snoopy->setContext( array( 'wikiroot'=>$remote_wiki_root ) );
 286+ # request login token
 287+ $api_params = array( 'action'=>'login', 'lgname'=>$remote_wiki_user, 'lgpassword'=>$remote_wiki_password, 'format'=>'json' );
 288+ $snoopy->submitToApi( $api_params );
 289+ # transport level error ?
 290+ if ( $snoopy->error != '' ) {
 291+ return $json_result->getResult( 'http', $snoopy->error );
 292+ }
 293+ $response = json_decode( $snoopy->results );
 294+ # proxy returned html instead of json ?
 295+ if ( $response === null ) {
 296+ return $json_result->getResult( 'http' );
 297+ }
 298+ if ( $response->login->result !== 'NeedToken' ) {
 299+ if ( $response->login->result === 'Success' ) {
 300+ # mediawiki version < 1.15
 301+ $response->login->result = 'Unsupported';
 302+ }
 303+ return $json_result->getResult( $response->login->result );
 304+ }
 305+ if ( !isset( $response->login->token ) ||
 306+ !isset( $response->login->cookieprefix ) ||
 307+ !isset( $response->login->sessionid ) ) {
 308+ # mediawiki version < 1.15.5 ?
 309+ return $json_result->getResult( 'Unsupported' );
 310+ }
 311+ # login with token given
 312+ $api_params['lgtoken'] = $response->login->token;
 313+ $session_cookie_name = $response->login->cookieprefix . '_session';
 314+ # construct session cookies
 315+ $snoopy->cookies[$session_cookie_name] = $response->login->sessionid;
 316+ $snoopy->submitToApi( $api_params );
 317+ # transport level error ?
 318+ if ( $snoopy->error != '' ) {
 319+ return $json_result->getResult( 'http', $snoopy->error );
 320+ }
 321+ $response = json_decode( $snoopy->results );
 322+ # proxy returned html instead of json ?
 323+ if ( $response === null ) {
 324+ return $json_result->getResult( 'http' );
 325+ }
 326+ if ( $response->login->result === 'Success' ) {
 327+ $json_result->setStatus( '1' ); // success
 328+ $r = array(
 329+ 'userid' => $response->login->lguserid,
 330+ 'username' => $response->login->lgusername, // may return a different one ?
 331+ 'token' => $response->login->lgtoken,
 332+ 'cookieprefix' => $response->login->cookieprefix,
 333+ 'sessionid' => $response->login->sessionid );
 334+ $json_result->append( $r );
 335+ }
 336+ return $json_result->getResult( $response->login->result );
 337+ }
 338+
 339+ /*
 340+ * Access to local API
 341+ * @param $api_params string in JSON format {key:val} or PHP array ($key=>val)
 342+ * @return result of local API query
 343+ */
 344+ static function localAPIwrap( $api_params ) {
 345+ if ( is_string( $api_params ) ) {
 346+ $api_params = json_decode( $api_params, true );
 347+ }
 348+ $req = new FauxRequest( $api_params );
 349+ $api = new ApiMain( $req );
 350+ $api->execute();
 351+ return $api->getResultData();
 352+ }
 353+
 354+ /*
 355+ * called via AJAX to perform API request on local wiki (HTTP GET)
 356+ * @param $args[0] : API query parameters line in JSON format {'key':'val'} or PHP associative array
 357+ * @param $args[1] : optional, type of result:
 358+ * RESULT_JSON_STRING : return encoded JSON string (default)
 359+ * RESULT_JSON_ARRAY : return JSON result in PHP array
 360+ * @return JSON result of local API query
 361+ */
 362+ static function localAPIget() {
 363+ # get params
 364+ $args = func_get_args();
 365+ $resultEncoding = self::RESULT_JSON_STRING;
 366+ if ( count( $args ) > 1 ) {
 367+ $resultEncoding = (int) $args[1];
 368+ }
 369+ if ( !in_array( $resultEncoding, array( self::RESULT_JSON_STRING, self::RESULT_JSON_ARRAY ) ) ) {
 370+ throw new MWException( 'Unsupported type of result (' . htmlspecialchars( $resultEncoding, ENT_COMPAT, 'UTF-8' ) . ' ) in ' . __METHOD__ );
 371+ }
 372+ $json_result = new WikiSyncJSONresult( $resultEncoding == self::RESULT_JSON_STRING );
 373+ if ( !WikiSyncSetup::initUser() ) {
 374+ # not enough priviledges to run this method
 375+ return $json_result->getResult( 'noaccess' );
 376+ }
 377+ $api_params = is_array( $args[0] ) ? $args[0] : json_decode( $args[0], true );
 378+ try {
 379+ $response = self::localAPIwrap( $api_params );
 380+ } catch ( Exception $e ) {
 381+ if ( $e instanceof MWException ) {
 382+ wfDebugLog( 'exception', $e->getLogMessage() );
 383+ }
 384+ return $json_result->getResult( 'exception', $e->getMessage() );
 385+ }
 386+ $json_result->append( $response ); // no HTTP error & valid AJAX
 387+ if ( isset( $response['error'] ) ) {
 388+ return $json_result->getResult( $response['error']['code'], $response['error']['info'] ); // API reported error
 389+ }
 390+ $json_result->setStatus( '1' ); // API success
 391+ return $json_result->getResult();
 392+ }
 393+
 394+ /*
 395+ * called via AJAX to perform API request on remote wiki (HTTP GET/POST)
 396+ * @param $args[0] : remote context in JSON format, keys
 397+ * { 'wikiroot':val, 'userid':val, 'username':val, 'logintoken':val, 'cookieprefix':val, 'sessionid':val }
 398+ * @param $args[1] : API query parameters string in JSON format {'key':'val'}, or PHP associative array
 399+ * @param $args[2] : optional, file list to upload in PHP array ('myfile' => '/dir/filename.ext');
 400+ * (will use POST method and 'multipart/form-data' encoding in such case)
 401+ * default '' - empty string value to use GET method
 402+ * @param $args[3] : optional, type of result:
 403+ * RESULT_JSON_STRING : return encoded JSON string (default)
 404+ * RESULT_JSON_ARRAY : return JSON result in PHP array
 405+ * RESULT_SNOOPY : return WikiSnoopy instance instead (raw result)
 406+ * (warning: in case '2' JSON in PHP array will be returned in case of error)
 407+ * @return JSON result of local API query
 408+ */
 409+ static function remoteAPIget() {
 410+ # get params
 411+ $args = func_get_args();
 412+ // by default will encode JSON result to string
 413+ $resultEncoding = self::RESULT_JSON_STRING;
 414+ if ( count( $args ) > 3 ) {
 415+ $resultEncoding = (int) $args[3];
 416+ }
 417+ $api_files = count( $args ) > 2 ? $args[2] : '';
 418+ # when there are files posted, use only 'multipart/form-data'
 419+ $useMultipart = is_array( $api_files );
 420+ $json_result = new WikiSyncJSONresult( $resultEncoding == self::RESULT_JSON_STRING );
 421+ if ( !WikiSyncSetup::initUser() ) {
 422+ # not enough priviledges to run this method
 423+ return $json_result->getResult( 'noaccess' );
 424+ }
 425+ # snoopy api_params are associative array
 426+ $api_params = is_array( $args[1] ) ? $args[1] : json_decode( $args[1], true );
 427+ $snoopy = new WikiSnoopy();
 428+ $snoopy->setContext( $args[0] );
 429+ # we always use POST method because it's less often cached by proxies
 430+ # HTTP caching is real evil for AJAX calls
 431+ $snoopy->httpmethod = 'POST';
 432+ if ( $useMultipart ) {
 433+ $snoopy->set_submit_multipart();
 434+ } else {
 435+ $snoopy->set_submit_normal();
 436+ }
 437+ $snoopy->submitToApi( $api_params, $api_files );
 438+ # transport level error ?
 439+ if ( $snoopy->error != '' ) {
 440+ return $json_result->getResult( 'http', $snoopy->error );
 441+ }
 442+ if ( $resultEncoding == self::RESULT_SNOOPY ) {
 443+ return $snoopy;
 444+ }
 445+ $response = json_decode( $snoopy->results, true );
 446+ # proxy returned html instead of json ?
 447+ if ( $response === null ) {
 448+ return $json_result->getResult( 'http' );
 449+ }
 450+ $json_result->append( $response ); // no HTTP error & valid AJAX
 451+ if ( isset( $response['error'] ) ) {
 452+ return $json_result->getResult( $response['error']['code'], $response['error']['info'] ); // API reported error
 453+ }
 454+ $json_result->setStatus( '1' ); // API success
 455+ return $json_result->getResult();
 456+ }
 457+
 458+ static function sourceAPIget( $APIparams ) {
 459+ if ( self::$directionToLocal ) {
 460+ $jr = self::remoteAPIget( self::$remoteContextJSON, $APIparams, '', self::RESULT_JSON_ARRAY );
 461+ } else {
 462+ $jr = self::localAPIget( $APIparams, self::RESULT_JSON_ARRAY );
 463+ }
 464+ return $jr;
 465+ }
 466+
 467+ static function destinationAPIget( $APIparams ) {
 468+ if ( self::$directionToLocal ) {
 469+ $jr = self::localAPIget( $APIparams, self::RESULT_JSON_ARRAY );
 470+ } else {
 471+ $jr = self::remoteAPIget( self::$remoteContextJSON, $APIparams, '', self::RESULT_JSON_ARRAY );
 472+ }
 473+ return $jr;
 474+ }
 475+
 476+ static function tempnam_sfx( $path, $suffix ) {
 477+ for ( $i = 0; $i < 10; $i++ ) {
 478+ $fname = $path . mt_rand( 10000, 99999 ) . $suffix;
 479+ $fp = @fopen( $fname, 'x' );
 480+ if ( $fp !== false ) {
 481+ break;
 482+ }
 483+ }
 484+ if ( $fp === false ) {
 485+ throw new MWException( 'Cannot create temporary file in ' . __METHOD__ . ' Please make sure you have writing permissions in \'' . $path . '\'' );
 486+ }
 487+ return array( $fname, $fp );
 488+ }
 489+
 490+ /**
 491+ * @param $args array of AJAX arguments call
 492+ * @param $min_args minimal number of $args method requires
 493+ * @return true on success; false on error
 494+ * @modifies self::$json_result, self::$remoteContextJSON, self::$client_params, self::$directionToLocal
 495+ */
 496+ static function initClient( $args, $min_args, $client_name ) {
 497+ # use default IIS / Apache execution time limit which is much larger than default PHP limit
 498+ set_time_limit( 300 );
 499+ self::$json_result = new WikiSyncJSONresult();
 500+ if ( !WikiSyncSetup::initUser() ) {
 501+ # not enough priviledges to run this method
 502+ self::$json_result->setCode( 'noaccess' );
 503+ return false;
 504+ }
 505+ if ( count( $args ) < $min_args ) {
 506+ self::$json_result->setCode( 'init_client', 'Not enough number of parameters in ' . __METHOD__ );
 507+ return false;
 508+ }
 509+ # remote context; used for remote API calls
 510+ self::$remoteContextJSON = $args[0];
 511+ self::$client_params = json_decode( $args[1], true );
 512+ if ( ($check_result = self::checkClientParameters( $client_name )) !== true ) {
 513+ self::$json_result->setCode( 'init_client', $check_result );
 514+ return false;
 515+ }
 516+ if ( !is_bool( self::$directionToLocal = self::$client_params['direction_to_local'] ) ) {
 517+ self::$json_result->setCode( 'init_client', 'Parameter "direction_to_local" is not boolean in ' . $client_name );
 518+ };
 519+ return true;
 520+ }
 521+
 522+ /**
 523+ * import xml data either into local or remote wiki, depending on self::$directionToLocal value
 524+ */
 525+ static function importXML( $dstImportToken, $xmldata ) {
 526+ global $wgUser, $wgTmpDirectory;
 527+ // {{{ bugfixes
 528+ global $wgSMTP;
 529+// global $wgMaxArticleSize, $wgMaxPPNodeCount, $wgMaxTemplateDepth, $wgMaxPPExpandDepth;
 530+ global $wgEnableEmail, $wgEnableUserEmail;
 531+ // }}}
 532+ list( $fname, $fp ) = self::tempnam_sfx( $wgTmpDirectory . '/', '.xml' );
 533+ $flen = strlen( $xmldata );
 534+ if ( @fwrite( $fp, $xmldata, $flen ) !== $flen ) {
 535+ throw new MWException( 'Cannot write xmldata to file ' . $fname . ' in ' . __METHOD__ . ' disk full?' );
 536+ }
 537+ fclose( $fp );
 538+ if ( self::$directionToLocal ) {
 539+ # suppress "pear mail" smtp bugs in EmailNotification::actuallyNotifyOnPageChange()
 540+ $wgSMTP = false;
 541+ $wgEnableEmail = false;
 542+ $wgEnableUserEmail = false;
 543+ /*
 544+ if ( $wgMaxArticleSize < 8192 ) {
 545+ $wgMaxArticleSize = 8192;
 546+ }
 547+ */
 548+ $json_result = new WikiSyncJSONresult( false );
 549+ $json_result->setCode( 'import' );
 550+ if( !$wgUser->isAllowed( 'importupload' ) ) {
 551+ @unlink( $fname );
 552+ return $json_result->getResult( 'no_import_rights' );
 553+ }
 554+ $source = ImportStreamSource::newFromFile( $fname );
 555+ if ( $source instanceof WikiErrorMsg ||
 556+ WikiError::isError( $source ) ) {
 557+ @unlink( $fname );
 558+ return $json_result->getResult( 'import', $source->getMessage() );
 559+ }
 560+ $importer = new WikiImporter( $source );
 561+ $reporter = new WikiSyncImportReporter( $importer, true, '', wfMsg( 'wikisync_log_imported_by' ) );
 562+ $result = $importer->doImport();
 563+ @fclose( $source->mHandle );
 564+ @unlink( $fname );
 565+ if ( $result instanceof WikiXmlError ) {
 566+ $r =
 567+ array(
 568+ 'line' => $result->mLine,
 569+ 'column' => $result->mColumn,
 570+ 'context' => $result->mByte . $result->mContext,
 571+ 'xmlerror' => xml_error_string( $result->mXmlError )
 572+ );
 573+ $json_result->append( $r );
 574+ return $json_result->getResult( 'import', $result->getMessage() );
 575+ } elseif ( WikiError::isError( $result ) ) {
 576+ return $json_result->getResult( 'import', $source->getMessage() );
 577+ }
 578+ $resultData = $reporter->getData();
 579+ $json_result->setStatus( '1' ); // API success
 580+ return $json_result->getResult();
 581+ } else {
 582+ $APIparams = array(
 583+ 'action' => 'import',
 584+ 'format' => 'json',
 585+ 'token' => $dstImportToken,
 586+ );
 587+ $APIfiles = array(
 588+ 'xml'=>$fname
 589+ );
 590+ // will POST 'multipart/form-data', because $APIfiles are defined
 591+ $jr = self::remoteAPIget( self::$remoteContextJSON, $APIparams, $APIfiles, self::RESULT_JSON_ARRAY );
 592+ @unlink( $fname );
 593+ return $jr;
 594+ }
 595+ }
 596+
 597+ /*
 598+ * called via AJAX to perform synchronization of one XML chunk from source to destination wiki
 599+ * @param $args[0] : remote context in JSON format, keys
 600+ * { 'wikiroot':val, 'userid':val, 'username':val, 'logintoken':val, 'cookieprefix':val, 'sessionid':val }
 601+ * @param $args[1] : client parameters line in JSON format {'key':'val'}
 602+ * @return JSON result query (success/error status and the continuation revid, when available)
 603+ */
 604+ static function syncXMLchunk() {
 605+ if ( !self::initClient( func_get_args(), 2, 'syncXMLchunk' ) ) {
 606+ return self::$json_result->getResult();
 607+ }
 608+ $json_result = self::$json_result;
 609+ $client_params = self::$client_params;
 610+ $APIparams = array(
 611+ 'action' => 'revisionhistory',
 612+ 'format' => 'json',
 613+ 'xmldump' => '',
 614+ 'dir' => 'newer',
 615+ 'startid' => $client_params['startid'],
 616+ 'limit' => $client_params['limit']
 617+ );
 618+ $result = self::sourceAPIget( $APIparams );
 619+ if ( $result['ws_status'] === '0' ) {
 620+ $result['ws_msg'] = 'source: ' . $result['ws_msg'] . ' (' . __METHOD__ . ')';
 621+ return json_encode( $result );
 622+ }
 623+ # collect the file titles existed in current chunk's revisions
 624+ $files = array();
 625+ foreach ( $result['query']['revisionhistory'] as $entry ) {
 626+ if ( $entry['namespace'] == NS_FILE && $entry['redirect'] === '0' ) {
 627+ $files[] = $entry;
 628+ }
 629+ }
 630+ if ( count( $files ) > 0 ) {
 631+ $json_result->append( array( 'files' => $files ) );
 632+ }
 633+ if ( isset( $result['query-continue'] ) ) {
 634+ $json_result->set( 'continue_startid', $result['query-continue']['revisionhistory']['startid'] );
 635+ }
 636+ $result = self::importXML( $client_params['dst_import_token'], $result['query']['exportxml'] );
 637+ if ( $result['ws_status'] === '0' ) {
 638+ $result['ws_msg'] = 'destination: ' . $result['ws_msg'] . ' (' . __METHOD__ . ')';
 639+ return json_encode( $result );
 640+ }
 641+ $json_result->setStatus( '1' ); // API success
 642+ return $json_result->getResult();
 643+ }
 644+
 645+ static function transformImageInfoResult( $result ) {
 646+ $titles = $sha1 = $sizes = $timestamps = array();
 647+ foreach ( $result['query']['pages'] as $entry ) {
 648+ if ( isset( $entry['imageinfo'] ) ) {
 649+ $titles[] = $entry['title'];
 650+ $sha1[] = $entry['imageinfo'][0]['sha1'];
 651+ $sizes[] = $entry['imageinfo'][0]['size'];
 652+ $timestamps[] = $entry['imageinfo'][0]['timestamp'];
 653+ }
 654+ }
 655+ return array( 'titles'=>$titles, 'sha1'=>$sha1, 'sizes'=>$sizes, 'timestamps'=>$timestamps );
 656+ }
 657+
 658+ /*
 659+ * called via AJAX to compare source and destination list of files
 660+ * @param $args[0] : remote context in JSON format, keys
 661+ * { 'wikiroot':val, 'userid':val, 'username':val, 'logintoken':val, 'cookieprefix':val, 'sessionid':val }
 662+ * @param $args[1] : client parameters line in JSON format {'key':'val'}
 663+ * @return JSON result query (success/error status and the list of changed files that has to be uploaded, when available)
 664+ */
 665+ static function findNewFiles() {
 666+ if ( !self::initClient( func_get_args(), 2, 'findNewFiles' ) ) {
 667+ return self::$json_result->getResult();
 668+ }
 669+ $json_result = self::$json_result;
 670+ $client_params = self::$client_params;
 671+ $filelist = array();
 672+ foreach ( $client_params['chunk_files'] as &$entry ) {
 673+ $title = 'File:' . $entry['title'];
 674+ if ( array_search( $title, $filelist ) === false ) {
 675+ $filelist[] = $title;
 676+ }
 677+ }
 678+ $APIparams = array(
 679+ 'action' => 'query',
 680+ 'format' => 'json',
 681+ 'prop' => 'imageinfo',
 682+ 'titles' => implode( '|', $filelist ),
 683+ 'iiprop' => 'timestamp|user|size|sha1'
 684+ );
 685+ $src_result = self::sourceAPIget( $APIparams );
 686+ if ( $src_result['ws_status'] === '0' ||
 687+ !isset( $src_result['query'] ) ||
 688+ !isset( $src_result['query']['pages'] ) ) {
 689+ $src_result['ws_msg'] = 'source: ' . $src_result['ws_msg'] . ' (' . __METHOD__ . ')';
 690+ return json_encode( $src_result );
 691+ }
 692+ $src_result = self::transformImageInfoResult( $src_result );
 693+ $dst_result = self::destinationAPIget( $APIparams );
 694+ if ( $dst_result['ws_status'] === '0' ||
 695+ !isset( $dst_result['query'] ) ||
 696+ !isset( $dst_result['query']['pages'] ) ) {
 697+ $dst_result['ws_msg'] = 'destination: ' . $dst_result['ws_msg'] . ' (' . __METHOD__ . ')';
 698+ return json_encode( $dst_result );
 699+ }
 700+ $dst_result = self::transformImageInfoResult( $dst_result );
 701+ $new_files = array();
 702+ foreach ( $src_result['titles'] as $src_key => &$src_title ) {
 703+ if ( ( $dst_key = array_search( $src_title, $dst_result['titles'] ) ) === false ||
 704+ $dst_result['sha1'][$dst_key] !== $src_result['sha1'][$src_key] ||
 705+ $dst_result['sizes'][$dst_key] !== $src_result['sizes'][$src_key] ||
 706+ $dst_result['timestamps'][$dst_key] !== $src_result['timestamps'][$src_key] ) {
 707+ $new_files[] = array(
 708+ 'title' => $src_title,
 709+ 'size' => $src_result['sizes'][$src_key],
 710+ 'timestamp' => $src_result['timestamps'][$src_key]
 711+ );
 712+ }
 713+ }
 714+ if ( count( $new_files ) > 0 ) {
 715+ $json_result->append( array( 'new_files' => $new_files ) );
 716+ }
 717+ $json_result->setStatus( '1' ); // API success
 718+ return $json_result->getResult();
 719+ }
 720+
 721+ static function chunkFilePath( WikiSnoopy $snoopy, $chunk_fname ) {
 722+ global $wgTmpDirectory;
 723+ return $wgTmpDirectory . '/' . $snoopy->logintoken . '_' . $snoopy->sessionid . '_' . $chunk_fname;
 724+ }
 725+
 726+ /*
 727+ * called via AJAX to transfer one chunk of file from source to destination wiki
 728+ * @param $args[0] : remote context in JSON format, keys
 729+ * { 'wikiroot':val, 'userid':val, 'username':val, 'logintoken':val, 'cookieprefix':val, 'sessionid':val }
 730+ * @param $args[1] : client parameters line in JSON format {'key':'val'}
 731+ * @return JSON result query (success/error status and the offset of next chunk, if available; offset = -1 when transfer is complete)
 732+ */
 733+ static function transferFileBlock() {
 734+ if ( !self::initClient( func_get_args(), 2, 'transferFileBlock' ) ) {
 735+ return self::$json_result->getResult();
 736+ }
 737+ $json_result = self::$json_result;
 738+ $client_params = self::$client_params;
 739+ if ( self::$directionToLocal ) {
 740+ # transfer the chunk of file from remote wiki to temporary local file via api
 741+ $APIparams = array(
 742+ 'action' => 'getfile',
 743+ 'format' => 'json',
 744+ 'title' => $client_params['title'],
 745+ 'timestamp' => $client_params['timestamp'],
 746+ 'offset' => $client_params['offset'],
 747+ 'blocklen' => $client_params['blocklen']
 748+ );
 749+ $snoopy = self::remoteAPIget( self::$remoteContextJSON, $APIparams, '', self::RESULT_SNOOPY );
 750+ $error = true;
 751+ $content_length = 0;
 752+ $content_length_header = 'Content-Length: ';
 753+ $chunk_fname = urlencode( $client_params['title'] );
 754+ if ( !isset( $snoopy->headers ) ) {
 755+ return $json_result->getResult( 'api_getfile', 'Recieved response without HTTP headers: ' . $snoopy->results );
 756+ }
 757+ $transfer_is_done = true;
 758+ foreach ( $snoopy->headers as &$header ) {
 759+ if ( strpos( $header, 'Content-Type: application/x-wiki' ) === 0 ) {
 760+ $error = false;
 761+ continue;
 762+ }
 763+ if ( strpos( $header, $content_length_header ) === 0 ) {
 764+ $content_length = (int) trim( substr( $header, strlen( $content_length_header ) ) );
 765+ continue;
 766+ }
 767+ preg_match( '`Content-Disposition: inline;filename\*=utf-8\'[A-Za-z_]{1,}?\'(.*)`', $header, $matches );
 768+ if ( count( $matches ) > 1 ) {
 769+ $chunk_fname = trim( $matches[1] );
 770+ continue;
 771+ }
 772+ preg_match( '`Content-Range: bytes (\d{1,}?)-(\d{1,}?)/(\d{1,})`', $header, $matches );
 773+ if ( count( $matches ) > 3 ) {
 774+ $end_of_content = (int) $matches[2];
 775+ $end_of_file = (int) $matches[3];
 776+ # partial content found, check, whether the transferred chunk is the last one
 777+ if ( $end_of_content + 1 < $end_of_file ) {
 778+ $transfer_is_done = false;
 779+ }
 780+ continue;
 781+ }
 782+ }
 783+ # series of bugchecks to prevent file corruption
 784+ if ( !$error ) {
 785+ if ( strlen( $snoopy->results ) !== $content_length ) {
 786+ $error = true;
 787+ $snoopy->results = "Truncated remote file block recieved in " . __METHOD__;
 788+ $json_result->append( array( 'ws_auto_retry' => '' ) );
 789+ }
 790+ }
 791+ if ( $error ) {
 792+ return $json_result->getResult( 'api_getfile', $snoopy->results );
 793+ }
 794+ $chunk_fpath = self::chunkFilePath( $snoopy, $chunk_fname );
 795+ $offset = $client_params['offset'];
 796+ $open_mode = ( $offset === 0 ) ? 'wb' : 'cb';
 797+ if ( ( $f = @fopen( $chunk_fpath, $open_mode ) ) === false ) {
 798+ return $json_result->getResult( 'fopen', 'Cannot create / open temporary file ' . $chunk_fpath . ' in ' . __METHOD__ );
 799+ }
 800+ $stat = fstat( $f );
 801+ if ( $stat['size'] !== $offset ) {
 802+ @fclose( $f );
 803+ return $json_result->getResult( 'fstat', 'Temporary file ' . $chunk_fpath . ' cannot be sparse. Current file size is (' . $stat['size'] . '), trying to write at pos (' . $offset . ') , in ' . __METHOD__ );
 804+ }
 805+ if ( @fseek( $f, $offset ) !== 0 ) {
 806+ @fclose( $f );
 807+ return $json_result->getResult( 'fopen', 'Cannot seek temporary file ' . $chunk_fpath . ' pos ' . $offset . ' in ' . __METHOD__ );
 808+ }
 809+ if ( @fwrite( $f, $snoopy->results, $content_length ) !== $content_length ) {
 810+ @fclose( $f );
 811+ return $json_result->getResult( 'fwrite', 'Error writing to ' . $chunk_fpath . ' Disk full? in ' . __METHOD__ );
 812+ }
 813+ @fclose( $f );
 814+ // API success
 815+ $result = array(
 816+ // return number of bytes read
 817+ 'numread' => $content_length,
 818+ // used to reconstruct temporary file path in uploader
 819+ 'chunk_fname' => $chunk_fname
 820+ );
 821+ if ( $transfer_is_done ) {
 822+ $result['done'] = '';
 823+ }
 824+ $json_result->append( $result );
 825+ $json_result->setStatus( '1' ); // API success
 826+ return $json_result->getResult();
 827+ } else {
 828+ # transfer the chunk of file from local wiki to temporary remote file via api
 829+ return $json_result->getResult( 'unimplemented', 'Synchronization of files from local to remote wiki is not implemented yet. Please turn off file synchronization and try again.' );
 830+ }
 831+ }
 832+
 833+ /*
 834+ * called via AJAX to transfer one chunk of file from source to destination wiki
 835+ * @param $args[0] : remote context in JSON format, keys
 836+ * { 'wikiroot':val, 'userid':val, 'username':val, 'logintoken':val, 'cookieprefix':val, 'sessionid':val }
 837+ * @param $args[1] : client parameters line in JSON format {'key':'val'}
 838+ * @return JSON result query (success/error status and the offset of next chunk, if available; offset = -1 when transfer is complete)
 839+ */
 840+ static function uploadLocalFile() {
 841+ if ( !self::initClient( func_get_args(), 2, 'uploadLocalFile' ) ) {
 842+ return self::$json_result->getResult();
 843+ }
 844+ $json_result = self::$json_result;
 845+ $client_params = self::$client_params;
 846+ if ( self::$directionToLocal ) {
 847+ # upload temporary local file on local wiki to current local file
 848+ $snoopy = new WikiSnoopy();
 849+ # todo: currently, we are using remote context to build local file name
 850+ $snoopy->setContext( self::$remoteContextJSON );
 851+ $chunk_fpath = self::chunkFilePath( $snoopy, $client_params['file_name'] );
 852+ if ( !file_exists( $chunk_fpath ) ) {
 853+ return $json_result->getResult( 'chunk_file', 'Temporary file ' . $chunk_fpath . ' does not exists in ' . __METHOD__ );
 854+ }
 855+ $filesize = filesize( $chunk_fpath );
 856+ # return resulting file size
 857+ $json_result->append( array( 'tmp_file_size' => $filesize ) );
 858+ if ( $filesize !== $client_params['file_size'] ) {
 859+ # append for error reporting in JS part
 860+ $json_result->append( array( 'chunk_fpath' => $chunk_fpath ) );
 861+ }
 862+ $localFileTitle = Title::newFromText( urldecode( $client_params['file_name'] ), NS_FILE );
 863+ if ( !($localFileTitle instanceof Title) ) {
 864+ return $json_result->getResult( 'local_file', 'Specified title ' . $client_params['file_name'] . ' is invalid in ' . __METHOD__ );
 865+ }
 866+ $localFile = wfLocalFile( $localFileTitle );
 867+ $status = $localFile->upload( $chunk_fpath, wfMsg( 'wikisync_log_uploaded_by' ), '', 0, false, wfTimestamp( TS_MW, $client_params['file_timestamp'] ) );
 868+ if ( !$status->isGood() ) {
 869+ return $json_result->getResult( 'upload', $status->getWikiText() );
 870+ }
 871+ if ( !unlink( $chunk_fpath ) ) {
 872+ return $json_result->getResult( 'chunk_file', 'Cannot unlink temporary file ' . $chunk_fpath . ' in ' . __METHOD__ );
 873+ }
 874+ // API success
 875+ $json_result->setStatus( '1' ); // API success
 876+ return $json_result->getResult();
 877+ } else {
 878+ # upload temporary remote file on remote wiki to current remote file via api
 879+ return $json_result->getResult( 'unimplemented', 'Uploading of files from local to remote wiki is not implemented yet. Please turn off file synchronization and try again.' );
 880+ }
 881+ }
 882+
 883+} /* end of WikiSyncClient class */
Property changes on: trunk/extensions/WikiSync/WikiSyncClient.php
___________________________________________________________________
Added: svn:eol-style
1884 + native
Index: trunk/extensions/WikiSync/WikiSync_i18n.php
@@ -0,0 +1,98 @@
 2+<?php
 3+
 4+/**
 5+ * Messages list.
 6+ */
 7+
 8+$messages = array();
 9+
 10+/** English (English)
 11+ * @author QuestPC
 12+ */
 13+$messages['en'] = array(
 14+ 'wikisync' => 'Wiki synchronization',
 15+ 'wikisync-desc' => 'Provides a [[Special:WikiSync|special page]] to synchronize recent changes of two wikis - local one and remote one.',
 16+ 'wikisync_direction' => 'Please choose the direction of synchronization',
 17+ 'wikisync_local_root' => 'Local wiki site root',
 18+ 'wikisync_remote_root' => 'Remote wiki site root',
 19+ 'wikisync_remote_log' => 'Remote operations log',
 20+ 'wikisync_clear_log' => 'Clear log',
 21+ 'wikisync_login_to_remote_wiki' => 'Login to remote wiki',
 22+ 'wikisync_remote_wiki_root' => 'Remote wiki root',
 23+ 'wikisync_remote_wiki_example' => 'path to api.php, for example: http://www.mediawiki.org/w',
 24+ 'wikisync_remote_wiki_user' => 'Remote wiki user name',
 25+ 'wikisync_remote_wiki_pass' => 'Remote wiki password',
 26+ 'wikisync_remote_login_button' => 'Log in',
 27+ 'wikisync_sync_files' => 'Synchronize files',
 28+ 'wikisync_synchronization_button' => 'Synchronize',
 29+ 'wikisync_log_imported_by' => 'Imported by [[Special:WikiSync]]',
 30+ 'wikisync_log_uploaded_by' => 'Uploaded by [[Special:WikiSync]]',
 31+ 'wikisync_api_result_unknown_action' => 'Unknown API action',
 32+ 'wikisync_api_result_exception' => 'Exception occured in local API call',
 33+ 'wikisync_api_result_noaccess' => 'Only members of (sysop, bureaucrat) groups can use site synchronization',
 34+ 'wikisync_api_result_invalid_parameter' => 'Invalid value of parameter',
 35+ 'wikisync_api_result_http' => 'HTTP error while querying data from remote API',
 36+ 'wikisync_api_result_Unsupported' => 'Your version of MediaWiki is unsupported (less than 1.15)',
 37+ 'wikisync_api_result_NoName' => 'You didn\'t set the lgname parameter',
 38+ 'wikisync_api_result_Illegal' => 'You provided an illegal username',
 39+ 'wikisync_api_result_NotExists' => 'The username you provided doesn\'t exist',
 40+ 'wikisync_api_result_EmptyPass' => 'You didn\'t set the lgpassword parameter or you left it empty',
 41+ 'wikisync_api_result_WrongPass' => 'The password you provided is incorrect',
 42+ 'wikisync_api_result_WrongPluginPass' => 'Same as WrongPass, returned when an authentication plugin rather than MediaWiki itself rejected the password',
 43+ 'wikisync_api_result_CreateBlocked' => 'The wiki tried to automatically create a new account for you, but your IP address has been blocked from account creation',
 44+ 'wikisync_api_result_Throttled' => 'You\'ve logged in too many times in a short time.',
 45+ 'wikisync_api_result_Blocked' => 'User is blocked',
 46+ 'wikisync_api_result_mustbeposted' => 'The login module requires a POST request',
 47+ 'wikisync_api_result_NeedToken' => 'Either you did not provide the login token or the sessionid cookie. Request again with the token and cookie given in this response',
 48+ 'wikisync_api_result_no_import_rights' => 'This user is not allowed to import xml dump files',
 49+ 'wikisync_api_result_Success' => 'Successfully logged into remote wiki site',
 50+ 'wikisync_js_last_op_error' => "Last operation returned an error\nCode: $1\nMsg: $2\nPress [OK] to retry last operation",
 51+ 'wikisync_js_synchronization_confirmation' => "Are you sure you want to synchronize\nfrom $1\nto $2\nstarting from revision $3?",
 52+ 'wikisync_js_synchronization_success' => 'Synchronization was completed successfully',
 53+ 'wikisync_js_already_synchronized' => 'Source and destination wikis seems to be already synchronized',
 54+ 'wikisync_js_sync_to_itself' => 'You cannot synchronize the wiki to itself',
 55+ 'wikisync_js_diff_search' => 'Looking for difference in destination revisions',
 56+ 'wikisync_js_revision' => 'Revision $1',
 57+ 'wikisync_js_file_size_mismatch' => 'Temporary file $1 size ($2 bytes) does not match required size ($3 bytes). Make sure the file $4 was not manually overwritten in repository of source wiki.'
 58+);
 59+
 60+/** Russian (Русский)
 61+ * @author QuestPC
 62+ */
 63+$messages['ru'] = array(
 64+ 'wikisync' => 'Синхронизация вики сайтов',
 65+ 'wikisync-desc' => 'Предоставляет специальную страницу [[Special:WikiSync]] для автоматической синхронизации последних изменений двух вики-сайтов - удалённого сайта и его локальной копии.',
 66+ 'wikisync_direction' => 'Пожалуйста выберите направление синхронизации',
 67+ 'wikisync_local_root' => 'Корневой адрес локального сайта',
 68+ 'wikisync_remote_root' => 'Корневой адрес удалённого сайта',
 69+ 'wikisync_remote_log' => 'Журнал удалённых действий',
 70+ 'wikisync_clear_log' => 'Очистить журнал',
 71+ 'wikisync_login_to_remote_wiki' => 'Зайти на удалённый сайт',
 72+ 'wikisync_remote_wiki_root' => 'Корневой адрес удалённого сайта',
 73+ 'wikisync_remote_wiki_example' => 'путь к api.php, например: http://www.mediawiki.org/w',
 74+ 'wikisync_remote_wiki_user' => 'Имя пользователя удалённого сайта',
 75+ 'wikisync_remote_wiki_pass' => 'Пароль на удалённом сайте',
 76+ 'wikisync_remote_login_button' => 'Зайти',
 77+ 'wikisync_sync_files' => 'Синхронизировать файлы',
 78+ 'wikisync_synchronization_button' => 'Синхронизировать',
 79+ 'wikisync_log_imported_by' => 'Импортировано с помощью [[Special:WikiSync]]',
 80+ 'wikisync_log_uploaded_by' => 'Загружено с помощью [[Special:WikiSync]]',
 81+ 'wikisync_api_result_unknown_action' => 'Неизвестное действие (action) API',
 82+ 'wikisync_api_result_noaccess' => 'Only members of (sysop, bureaucrat) groups can use site synchronization',
 83+ 'wikisync_api_result_Illegal' => 'Недопустимое имя пользователя',
 84+ 'wikisync_api_result_NotExists' => 'Такого пользователя не существует',
 85+ 'wikisync_api_result_WrongPass' => 'Неверный пароль',
 86+ 'wikisync_api_result_WrongPluginPass' => 'Неверный пароль для плагина авторизации',
 87+ 'wikisync_api_result_Throttled' => 'Слишком много логинов в течение короткого времени.',
 88+ 'wikisync_api_result_Blocked' => 'Пользователь заблокирован',
 89+ 'wikisync_api_result_no_import_rights' => 'У пользователя нет прав на импортирование xml дампов',
 90+ 'wikisync_api_result_Success' => 'Успешный заход на удалённый вики сайт',
 91+ 'wikisync_js_last_op_error' => "Последнее действие вызвало ошибку\nКод ошибки: $1\nСообщение: $2\nНажмите [OK], чтобы попытаться повторить последнее действие",
 92+ 'wikisync_js_synchronization_confirmation' => "Вы уверены в том что хотите синхронизировать последние изменения\nс $1\nна $2\nначиная с ревизии $3?",
 93+ 'wikisync_js_synchronization_success' => 'Синхронизация успешно завершена',
 94+ 'wikisync_js_already_synchronized' => 'Исходный и назначенный вики-сайты выглядят уже синхронизированными',
 95+ 'wikisync_js_sync_to_itself' => 'Невозможно синхронизировать вики сайт сам в себя',
 96+ 'wikisync_js_diff_search' => 'Поиск отличий в ревизиях вики-сайта назначения',
 97+ 'wikisync_js_revision' => 'Ревизия $1',
 98+ 'wikisync_js_file_size_mismatch' => 'Размер временного файла $1 ($2 байт) не соответствует требуемому размеру файла ($3 байт). Пожалуйста убедитесь, что файл $4 не был переписан вручную в репозиторий исходного вики-сайта.'
 99+);
Property changes on: trunk/extensions/WikiSync/WikiSync_i18n.php
___________________________________________________________________
Added: svn:eol-style
1100 + native
Index: trunk/extensions/WikiSync/WikiSync.php
@@ -0,0 +1,152 @@
 2+<?php
 3+/**
 4+ * ***** BEGIN LICENSE BLOCK *****
 5+ * This file is part of WikiSync.
 6+ *
 7+ * WikiSync 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+ * WikiSync 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
 18+ * along with WikiSync; if not, write to the Free Software
 19+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 20+ *
 21+ * ***** END LICENSE BLOCK *****
 22+ *
 23+ * WikiSync allows an AJAX-based synchronization of revisions and files between
 24+ * global wiki site and it's local mirror.
 25+ *
 26+ * To activate this extension :
 27+ * * Create a new directory named WikiSync into the directory "extensions" of MediaWiki.
 28+ * * Place the files from the extension archive there.
 29+ * * Add this line at the end of your LocalSettings.php file :
 30+ * require_once "$IP/extensions/WikiSync/WikiSync.php";
 31+ *
 32+ * @version 0.2.0
 33+ * @link http://www.mediawiki.org/wiki/Extension:WikiSync
 34+ * @author Dmitriy Sintsov <questpc@rambler.ru>
 35+ * @addtogroup Extensions
 36+ */
 37+
 38+if ( !defined( 'MEDIAWIKI' ) ) {
 39+ die( "This file is a part of MediaWiki extension.\n" );
 40+}
 41+
 42+$wgExtensionCredits['specialpage'][] = array(
 43+ 'path' => __FILE__,
 44+ 'name' => 'WikiSync',
 45+ 'author' => 'QuestPC',
 46+ 'url' => 'http://www.mediawiki.org/wiki/Extension:WikiSync',
 47+ 'descriptionmsg' => 'wikisync-desc',
 48+);
 49+
 50+$dir = dirname(__FILE__);
 51+$wgExtensionMessagesFiles['WikiSync'] = $dir . '/WikiSync_i18n.php';
 52+$wgExtensionAliasesFiles['WikiSync'] = $dir . '/WikiSync.alias.php';
 53+$wgSpecialPages['WikiSync'] = array( 'WikiSyncPage' );
 54+$wgSpecialPageGroups['WikiSync'] = 'pagetools';
 55+
 56+WikiSyncSetup::init();
 57+
 58+if ( !function_exists( 'json_decode' ) ) {
 59+ function json_decode( $content, $assoc = false ) {
 60+ if ( $assoc ) {
 61+ $json = new Services_JSON( SERVICES_JSON_LOOSE_TYPE );
 62+ } else {
 63+ $json = new Services_JSON;
 64+ }
 65+ return $json->decode( $content );
 66+ }
 67+}
 68+
 69+if ( !function_exists( 'json_encode' ) ) {
 70+ function json_encode( $content ) {
 71+ $json = new Services_JSON;
 72+ return $json->encode($content);
 73+ }
 74+}
 75+
 76+class WikiSyncSetup {
 77+ # {{{ changable in LocalSettings.php :
 78+ static $remote_wiki_root = 'http://www.mediawiki.org/w';
 79+ static $remote_wiki_user = 'Username';
 80+ static $proxy_address = ''; # 'http://10.0.0.78:3128';
 81+ # }}}
 82+
 83+ # {{{ decoded local proxy settings
 84+ static $proxy_host = '';
 85+ static $proxy_port = '';
 86+ static $proxy_user = '';
 87+ static $proxy_pass = '';
 88+ # }}}
 89+
 90+ static $version = '0.2.0'; // version of extension
 91+ static $ExtDir; // filesys path with windows path fix
 92+ static $ScriptPath; // apache virtual path
 93+
 94+ static function init() {
 95+ global $wgScriptPath;
 96+ global $wgAutoloadClasses;
 97+ global $wgAjaxExportList;
 98+ global $wgAPIModules;
 99+
 100+ self::$ExtDir = str_replace( "\\", "/", dirname( __FILE__ ) );
 101+ $top_dir = explode( '/', self::$ExtDir );
 102+ $top_dir = array_pop( $top_dir );
 103+ self::$ScriptPath = $wgScriptPath . '/extensions' . ( ( $top_dir == 'extensions' ) ? '' : '/' . $top_dir );
 104+
 105+ if ( !isset( $wgAutoloadClasses['_QXML'] ) ) {
 106+ $wgAutoloadClasses['_QXML'] = self::$ExtDir . '/WikiSyncBasic.php';
 107+ }
 108+ $wgAutoloadClasses['Snoopy'] = self::$ExtDir . '/Snoopy/Snoopy.class.php';
 109+ $wgAutoloadClasses['Services_JSON'] = self::$ExtDir . '/pear/JSON.php';
 110+ $wgAutoloadClasses['WikiSnoopy'] =
 111+ $wgAutoloadClasses['WikiSyncJSONresult'] =
 112+ $wgAutoloadClasses['WikiSyncClient'] = self::$ExtDir . '/WikiSyncClient.php';
 113+ $wgAutoloadClasses['WikiSyncPage'] = self::$ExtDir . '/WikiSyncPage.php';
 114+ $wgAutoloadClasses['WikiSyncExporter'] =
 115+ $wgAutoloadClasses['WikiSyncImportReporter'] = self::$ExtDir . '/WikiSyncExporter.php';
 116+ $wgAutoloadClasses['ApiWikiSync'] =
 117+ $wgAutoloadClasses['ApiRevisionHistory'] =
 118+ $wgAutoloadClasses['ApiFindSimilarRev'] =
 119+ $wgAutoloadClasses['ApiGetFile'] = self::$ExtDir . '/WikiSyncApi.php';
 120+
 121+ $wgAPIModules['revisionhistory'] = 'ApiRevisionHistory';
 122+ $wgAPIModules['similarrev'] = 'ApiFindSimilarRev';
 123+ $wgAPIModules['getfile'] = 'ApiGetFile';
 124+
 125+ $wgAjaxExportList[] = 'WikiSyncClient::remoteLogin';
 126+ $wgAjaxExportList[] = 'WikiSyncClient::localAPIget';
 127+ $wgAjaxExportList[] = 'WikiSyncClient::remoteAPIget';
 128+ $wgAjaxExportList[] = 'WikiSyncClient::syncXMLchunk';
 129+ $wgAjaxExportList[] = 'WikiSyncClient::findNewFiles';
 130+ $wgAjaxExportList[] = 'WikiSyncClient::transferFileBlock';
 131+ $wgAjaxExportList[] = 'WikiSyncClient::uploadLocalFile';
 132+
 133+ if ( ($parsed_url = parse_url( self::$proxy_address )) !== false ) {
 134+ if ( isset( $parsed_url['host'] ) ) { self::$proxy_host = $parsed_url['host']; }
 135+ if ( isset( $parsed_url['port'] ) ) { self::$proxy_port = $parsed_url['port']; }
 136+ if ( isset( $parsed_url['user'] ) ) { self::$proxy_user = $parsed_url['user']; }
 137+ if ( isset( $parsed_url['pass'] ) ) { self::$proxy_pass = $parsed_url['pass']; }
 138+ }
 139+ }
 140+
 141+ /*
 142+ * should not be called from LocalSettings.php
 143+ * should be called only when the wiki is fully initialized
 144+ * @return true, when the current user has admin rights, false otherwise
 145+ */
 146+ static function initUser() {
 147+ global $wgUser;
 148+ wfLoadExtensionMessages( 'WikiSync' );
 149+ $ug = $wgUser->getEffectiveGroups();
 150+ return array_intersect( array( 'sysop', 'bureaucrat' ), $ug );
 151+ }
 152+
 153+} /* end of WikiSyncSetup class */
Property changes on: trunk/extensions/WikiSync/WikiSync.php
___________________________________________________________________
Added: svn:eol-style
1154 + native
Index: trunk/extensions/WikiSync/pear/JSON.php
@@ -0,0 +1,806 @@
 2+<?php
 3+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
 4+
 5+/**
 6+ * Converts to and from JSON format.
 7+ *
 8+ * JSON (JavaScript Object Notation) is a lightweight data-interchange
 9+ * format. It is easy for humans to read and write. It is easy for machines
 10+ * to parse and generate. It is based on a subset of the JavaScript
 11+ * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
 12+ * This feature can also be found in Python. JSON is a text format that is
 13+ * completely language independent but uses conventions that are familiar
 14+ * to programmers of the C-family of languages, including C, C++, C#, Java,
 15+ * JavaScript, Perl, TCL, and many others. These properties make JSON an
 16+ * ideal data-interchange language.
 17+ *
 18+ * This package provides a simple encoder and decoder for JSON notation. It
 19+ * is intended for use with client-side Javascript applications that make
 20+ * use of HTTPRequest to perform server communication functions - data can
 21+ * be encoded into JSON notation for use in a client-side javascript, or
 22+ * decoded from incoming Javascript requests. JSON format is native to
 23+ * Javascript, and can be directly eval()'ed with no further parsing
 24+ * overhead
 25+ *
 26+ * All strings should be in ASCII or UTF-8 format!
 27+ *
 28+ * LICENSE: Redistribution and use in source and binary forms, with or
 29+ * without modification, are permitted provided that the following
 30+ * conditions are met: Redistributions of source code must retain the
 31+ * above copyright notice, this list of conditions and the following
 32+ * disclaimer. Redistributions in binary form must reproduce the above
 33+ * copyright notice, this list of conditions and the following disclaimer
 34+ * in the documentation and/or other materials provided with the
 35+ * distribution.
 36+ *
 37+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 38+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 39+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
 40+ * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 41+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 42+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 43+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 44+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 45+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 46+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 47+ * DAMAGE.
 48+ *
 49+ * @category
 50+ * @package Services_JSON
 51+ * @author Michal Migurski <mike-json@teczno.com>
 52+ * @author Matt Knapp <mdknapp[at]gmail[dot]com>
 53+ * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
 54+ * @copyright 2005 Michal Migurski
 55+ * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
 56+ * @license http://www.opensource.org/licenses/bsd-license.php
 57+ * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
 58+ */
 59+
 60+/**
 61+ * Marker constant for Services_JSON::decode(), used to flag stack state
 62+ */
 63+define('SERVICES_JSON_SLICE', 1);
 64+
 65+/**
 66+ * Marker constant for Services_JSON::decode(), used to flag stack state
 67+ */
 68+define('SERVICES_JSON_IN_STR', 2);
 69+
 70+/**
 71+ * Marker constant for Services_JSON::decode(), used to flag stack state
 72+ */
 73+define('SERVICES_JSON_IN_ARR', 3);
 74+
 75+/**
 76+ * Marker constant for Services_JSON::decode(), used to flag stack state
 77+ */
 78+define('SERVICES_JSON_IN_OBJ', 4);
 79+
 80+/**
 81+ * Marker constant for Services_JSON::decode(), used to flag stack state
 82+ */
 83+define('SERVICES_JSON_IN_CMT', 5);
 84+
 85+/**
 86+ * Behavior switch for Services_JSON::decode()
 87+ */
 88+define('SERVICES_JSON_LOOSE_TYPE', 16);
 89+
 90+/**
 91+ * Behavior switch for Services_JSON::decode()
 92+ */
 93+define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
 94+
 95+/**
 96+ * Converts to and from JSON format.
 97+ *
 98+ * Brief example of use:
 99+ *
 100+ * <code>
 101+ * // create a new instance of Services_JSON
 102+ * $json = new Services_JSON();
 103+ *
 104+ * // convert a complexe value to JSON notation, and send it to the browser
 105+ * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
 106+ * $output = $json->encode($value);
 107+ *
 108+ * print($output);
 109+ * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
 110+ *
 111+ * // accept incoming POST data, assumed to be in JSON notation
 112+ * $input = file_get_contents('php://input', 1000000);
 113+ * $value = $json->decode($input);
 114+ * </code>
 115+ */
 116+class Services_JSON
 117+{
 118+ /**
 119+ * constructs a new JSON instance
 120+ *
 121+ * @param int $use object behavior flags; combine with boolean-OR
 122+ *
 123+ * possible values:
 124+ * - SERVICES_JSON_LOOSE_TYPE: loose typing.
 125+ * "{...}" syntax creates associative arrays
 126+ * instead of objects in decode().
 127+ * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
 128+ * Values which can't be encoded (e.g. resources)
 129+ * appear as NULL instead of throwing errors.
 130+ * By default, a deeply-nested resource will
 131+ * bubble up with an error, so all return values
 132+ * from encode() should be checked with isError()
 133+ */
 134+ function Services_JSON($use = 0)
 135+ {
 136+ $this->use = $use;
 137+ }
 138+
 139+ /**
 140+ * convert a string from one UTF-16 char to one UTF-8 char
 141+ *
 142+ * Normally should be handled by mb_convert_encoding, but
 143+ * provides a slower PHP-only method for installations
 144+ * that lack the multibye string extension.
 145+ *
 146+ * @param string $utf16 UTF-16 character
 147+ * @return string UTF-8 character
 148+ * @access private
 149+ */
 150+ function utf162utf8($utf16)
 151+ {
 152+ // oh please oh please oh please oh please oh please
 153+ if(function_exists('mb_convert_encoding')) {
 154+ return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
 155+ }
 156+
 157+ $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
 158+
 159+ switch(true) {
 160+ case ((0x7F & $bytes) == $bytes):
 161+ // this case should never be reached, because we are in ASCII range
 162+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
 163+ return chr(0x7F & $bytes);
 164+
 165+ case (0x07FF & $bytes) == $bytes:
 166+ // return a 2-byte UTF-8 character
 167+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
 168+ return chr(0xC0 | (($bytes >> 6) & 0x1F))
 169+ . chr(0x80 | ($bytes & 0x3F));
 170+
 171+ case (0xFFFF & $bytes) == $bytes:
 172+ // return a 3-byte UTF-8 character
 173+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
 174+ return chr(0xE0 | (($bytes >> 12) & 0x0F))
 175+ . chr(0x80 | (($bytes >> 6) & 0x3F))
 176+ . chr(0x80 | ($bytes & 0x3F));
 177+ }
 178+
 179+ // ignoring UTF-32 for now, sorry
 180+ return '';
 181+ }
 182+
 183+ /**
 184+ * convert a string from one UTF-8 char to one UTF-16 char
 185+ *
 186+ * Normally should be handled by mb_convert_encoding, but
 187+ * provides a slower PHP-only method for installations
 188+ * that lack the multibye string extension.
 189+ *
 190+ * @param string $utf8 UTF-8 character
 191+ * @return string UTF-16 character
 192+ * @access private
 193+ */
 194+ function utf82utf16($utf8)
 195+ {
 196+ // oh please oh please oh please oh please oh please
 197+ if(function_exists('mb_convert_encoding')) {
 198+ return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
 199+ }
 200+
 201+ switch(strlen($utf8)) {
 202+ case 1:
 203+ // this case should never be reached, because we are in ASCII range
 204+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
 205+ return $utf8;
 206+
 207+ case 2:
 208+ // return a UTF-16 character from a 2-byte UTF-8 char
 209+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
 210+ return chr(0x07 & (ord($utf8{0}) >> 2))
 211+ . chr((0xC0 & (ord($utf8{0}) << 6))
 212+ | (0x3F & ord($utf8{1})));
 213+
 214+ case 3:
 215+ // return a UTF-16 character from a 3-byte UTF-8 char
 216+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
 217+ return chr((0xF0 & (ord($utf8{0}) << 4))
 218+ | (0x0F & (ord($utf8{1}) >> 2)))
 219+ . chr((0xC0 & (ord($utf8{1}) << 6))
 220+ | (0x7F & ord($utf8{2})));
 221+ }
 222+
 223+ // ignoring UTF-32 for now, sorry
 224+ return '';
 225+ }
 226+
 227+ /**
 228+ * encodes an arbitrary variable into JSON format
 229+ *
 230+ * @param mixed $var any number, boolean, string, array, or object to be encoded.
 231+ * see argument 1 to Services_JSON() above for array-parsing behavior.
 232+ * if var is a strng, note that encode() always expects it
 233+ * to be in ASCII or UTF-8 format!
 234+ *
 235+ * @return mixed JSON string representation of input var or an error if a problem occurs
 236+ * @access public
 237+ */
 238+ function encode($var)
 239+ {
 240+ switch (gettype($var)) {
 241+ case 'boolean':
 242+ return $var ? 'true' : 'false';
 243+
 244+ case 'NULL':
 245+ return 'null';
 246+
 247+ case 'integer':
 248+ return (int) $var;
 249+
 250+ case 'double':
 251+ case 'float':
 252+ return (float) $var;
 253+
 254+ case 'string':
 255+ // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
 256+ $ascii = '';
 257+ $strlen_var = strlen($var);
 258+
 259+ /*
 260+ * Iterate over every character in the string,
 261+ * escaping with a slash or encoding to UTF-8 where necessary
 262+ */
 263+ for ($c = 0; $c < $strlen_var; ++$c) {
 264+
 265+ $ord_var_c = ord($var{$c});
 266+
 267+ switch (true) {
 268+ case $ord_var_c == 0x08:
 269+ $ascii .= '\b';
 270+ break;
 271+ case $ord_var_c == 0x09:
 272+ $ascii .= '\t';
 273+ break;
 274+ case $ord_var_c == 0x0A:
 275+ $ascii .= '\n';
 276+ break;
 277+ case $ord_var_c == 0x0C:
 278+ $ascii .= '\f';
 279+ break;
 280+ case $ord_var_c == 0x0D:
 281+ $ascii .= '\r';
 282+ break;
 283+
 284+ case $ord_var_c == 0x22:
 285+ case $ord_var_c == 0x2F:
 286+ case $ord_var_c == 0x5C:
 287+ // double quote, slash, slosh
 288+ $ascii .= '\\'.$var{$c};
 289+ break;
 290+
 291+ case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
 292+ // characters U-00000000 - U-0000007F (same as ASCII)
 293+ $ascii .= $var{$c};
 294+ break;
 295+
 296+ case (($ord_var_c & 0xE0) == 0xC0):
 297+ // characters U-00000080 - U-000007FF, mask 110XXXXX
 298+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
 299+ $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
 300+ $c += 1;
 301+ $utf16 = $this->utf82utf16($char);
 302+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
 303+ break;
 304+
 305+ case (($ord_var_c & 0xF0) == 0xE0):
 306+ // characters U-00000800 - U-0000FFFF, mask 1110XXXX
 307+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
 308+ $char = pack('C*', $ord_var_c,
 309+ ord($var{$c + 1}),
 310+ ord($var{$c + 2}));
 311+ $c += 2;
 312+ $utf16 = $this->utf82utf16($char);
 313+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
 314+ break;
 315+
 316+ case (($ord_var_c & 0xF8) == 0xF0):
 317+ // characters U-00010000 - U-001FFFFF, mask 11110XXX
 318+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
 319+ $char = pack('C*', $ord_var_c,
 320+ ord($var{$c + 1}),
 321+ ord($var{$c + 2}),
 322+ ord($var{$c + 3}));
 323+ $c += 3;
 324+ $utf16 = $this->utf82utf16($char);
 325+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
 326+ break;
 327+
 328+ case (($ord_var_c & 0xFC) == 0xF8):
 329+ // characters U-00200000 - U-03FFFFFF, mask 111110XX
 330+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
 331+ $char = pack('C*', $ord_var_c,
 332+ ord($var{$c + 1}),
 333+ ord($var{$c + 2}),
 334+ ord($var{$c + 3}),
 335+ ord($var{$c + 4}));
 336+ $c += 4;
 337+ $utf16 = $this->utf82utf16($char);
 338+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
 339+ break;
 340+
 341+ case (($ord_var_c & 0xFE) == 0xFC):
 342+ // characters U-04000000 - U-7FFFFFFF, mask 1111110X
 343+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
 344+ $char = pack('C*', $ord_var_c,
 345+ ord($var{$c + 1}),
 346+ ord($var{$c + 2}),
 347+ ord($var{$c + 3}),
 348+ ord($var{$c + 4}),
 349+ ord($var{$c + 5}));
 350+ $c += 5;
 351+ $utf16 = $this->utf82utf16($char);
 352+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
 353+ break;
 354+ }
 355+ }
 356+
 357+ return '"'.$ascii.'"';
 358+
 359+ case 'array':
 360+ /*
 361+ * As per JSON spec if any array key is not an integer
 362+ * we must treat the the whole array as an object. We
 363+ * also try to catch a sparsely populated associative
 364+ * array with numeric keys here because some JS engines
 365+ * will create an array with empty indexes up to
 366+ * max_index which can cause memory issues and because
 367+ * the keys, which may be relevant, will be remapped
 368+ * otherwise.
 369+ *
 370+ * As per the ECMA and JSON specification an object may
 371+ * have any string as a property. Unfortunately due to
 372+ * a hole in the ECMA specification if the key is a
 373+ * ECMA reserved word or starts with a digit the
 374+ * parameter is only accessible using ECMAScript's
 375+ * bracket notation.
 376+ */
 377+
 378+ // treat as a JSON object
 379+ if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
 380+ $properties = array_map(array($this, 'name_value'),
 381+ array_keys($var),
 382+ array_values($var));
 383+
 384+ foreach($properties as $property) {
 385+ if(Services_JSON::isError($property)) {
 386+ return $property;
 387+ }
 388+ }
 389+
 390+ return '{' . join(',', $properties) . '}';
 391+ }
 392+
 393+ // treat it like a regular array
 394+ $elements = array_map(array($this, 'encode'), $var);
 395+
 396+ foreach($elements as $element) {
 397+ if(Services_JSON::isError($element)) {
 398+ return $element;
 399+ }
 400+ }
 401+
 402+ return '[' . join(',', $elements) . ']';
 403+
 404+ case 'object':
 405+ $vars = get_object_vars($var);
 406+
 407+ $properties = array_map(array($this, 'name_value'),
 408+ array_keys($vars),
 409+ array_values($vars));
 410+
 411+ foreach($properties as $property) {
 412+ if(Services_JSON::isError($property)) {
 413+ return $property;
 414+ }
 415+ }
 416+
 417+ return '{' . join(',', $properties) . '}';
 418+
 419+ default:
 420+ return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
 421+ ? 'null'
 422+ : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
 423+ }
 424+ }
 425+
 426+ /**
 427+ * array-walking function for use in generating JSON-formatted name-value pairs
 428+ *
 429+ * @param string $name name of key to use
 430+ * @param mixed $value reference to an array element to be encoded
 431+ *
 432+ * @return string JSON-formatted name-value pair, like '"name":value'
 433+ * @access private
 434+ */
 435+ function name_value($name, $value)
 436+ {
 437+ $encoded_value = $this->encode($value);
 438+
 439+ if(Services_JSON::isError($encoded_value)) {
 440+ return $encoded_value;
 441+ }
 442+
 443+ return $this->encode(strval($name)) . ':' . $encoded_value;
 444+ }
 445+
 446+ /**
 447+ * reduce a string by removing leading and trailing comments and whitespace
 448+ *
 449+ * @param $str string string value to strip of comments and whitespace
 450+ *
 451+ * @return string string value stripped of comments and whitespace
 452+ * @access private
 453+ */
 454+ function reduce_string($str)
 455+ {
 456+ $str = preg_replace(array(
 457+
 458+ // eliminate single line comments in '// ...' form
 459+ '#^\s*//(.+)$#m',
 460+
 461+ // eliminate multi-line comments in '/* ... */' form, at start of string
 462+ '#^\s*/\*(.+)\*/#Us',
 463+
 464+ // eliminate multi-line comments in '/* ... */' form, at end of string
 465+ '#/\*(.+)\*/\s*$#Us'
 466+
 467+ ), '', $str);
 468+
 469+ // eliminate extraneous space
 470+ return trim($str);
 471+ }
 472+
 473+ /**
 474+ * decodes a JSON string into appropriate variable
 475+ *
 476+ * @param string $str JSON-formatted string
 477+ *
 478+ * @return mixed number, boolean, string, array, or object
 479+ * corresponding to given JSON input string.
 480+ * See argument 1 to Services_JSON() above for object-output behavior.
 481+ * Note that decode() always returns strings
 482+ * in ASCII or UTF-8 format!
 483+ * @access public
 484+ */
 485+ function decode($str)
 486+ {
 487+ $str = $this->reduce_string($str);
 488+
 489+ switch (strtolower($str)) {
 490+ case 'true':
 491+ return true;
 492+
 493+ case 'false':
 494+ return false;
 495+
 496+ case 'null':
 497+ return null;
 498+
 499+ default:
 500+ $m = array();
 501+
 502+ if (is_numeric($str)) {
 503+ // Lookie-loo, it's a number
 504+
 505+ // This would work on its own, but I'm trying to be
 506+ // good about returning integers where appropriate:
 507+ // return (float)$str;
 508+
 509+ // Return float or int, as appropriate
 510+ return ((float)$str == (integer)$str)
 511+ ? (integer)$str
 512+ : (float)$str;
 513+
 514+ } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
 515+ // STRINGS RETURNED IN UTF-8 FORMAT
 516+ $delim = substr($str, 0, 1);
 517+ $chrs = substr($str, 1, -1);
 518+ $utf8 = '';
 519+ $strlen_chrs = strlen($chrs);
 520+
 521+ for ($c = 0; $c < $strlen_chrs; ++$c) {
 522+
 523+ $substr_chrs_c_2 = substr($chrs, $c, 2);
 524+ $ord_chrs_c = ord($chrs{$c});
 525+
 526+ switch (true) {
 527+ case $substr_chrs_c_2 == '\b':
 528+ $utf8 .= chr(0x08);
 529+ ++$c;
 530+ break;
 531+ case $substr_chrs_c_2 == '\t':
 532+ $utf8 .= chr(0x09);
 533+ ++$c;
 534+ break;
 535+ case $substr_chrs_c_2 == '\n':
 536+ $utf8 .= chr(0x0A);
 537+ ++$c;
 538+ break;
 539+ case $substr_chrs_c_2 == '\f':
 540+ $utf8 .= chr(0x0C);
 541+ ++$c;
 542+ break;
 543+ case $substr_chrs_c_2 == '\r':
 544+ $utf8 .= chr(0x0D);
 545+ ++$c;
 546+ break;
 547+
 548+ case $substr_chrs_c_2 == '\\"':
 549+ case $substr_chrs_c_2 == '\\\'':
 550+ case $substr_chrs_c_2 == '\\\\':
 551+ case $substr_chrs_c_2 == '\\/':
 552+ if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
 553+ ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
 554+ $utf8 .= $chrs{++$c};
 555+ }
 556+ break;
 557+
 558+ case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
 559+ // single, escaped unicode character
 560+ $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
 561+ . chr(hexdec(substr($chrs, ($c + 4), 2)));
 562+ $utf8 .= $this->utf162utf8($utf16);
 563+ $c += 5;
 564+ break;
 565+
 566+ case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
 567+ $utf8 .= $chrs{$c};
 568+ break;
 569+
 570+ case ($ord_chrs_c & 0xE0) == 0xC0:
 571+ // characters U-00000080 - U-000007FF, mask 110XXXXX
 572+ //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
 573+ $utf8 .= substr($chrs, $c, 2);
 574+ ++$c;
 575+ break;
 576+
 577+ case ($ord_chrs_c & 0xF0) == 0xE0:
 578+ // characters U-00000800 - U-0000FFFF, mask 1110XXXX
 579+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
 580+ $utf8 .= substr($chrs, $c, 3);
 581+ $c += 2;
 582+ break;
 583+
 584+ case ($ord_chrs_c & 0xF8) == 0xF0:
 585+ // characters U-00010000 - U-001FFFFF, mask 11110XXX
 586+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
 587+ $utf8 .= substr($chrs, $c, 4);
 588+ $c += 3;
 589+ break;
 590+
 591+ case ($ord_chrs_c & 0xFC) == 0xF8:
 592+ // characters U-00200000 - U-03FFFFFF, mask 111110XX
 593+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
 594+ $utf8 .= substr($chrs, $c, 5);
 595+ $c += 4;
 596+ break;
 597+
 598+ case ($ord_chrs_c & 0xFE) == 0xFC:
 599+ // characters U-04000000 - U-7FFFFFFF, mask 1111110X
 600+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
 601+ $utf8 .= substr($chrs, $c, 6);
 602+ $c += 5;
 603+ break;
 604+
 605+ }
 606+
 607+ }
 608+
 609+ return $utf8;
 610+
 611+ } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
 612+ // array, or object notation
 613+
 614+ if ($str{0} == '[') {
 615+ $stk = array(SERVICES_JSON_IN_ARR);
 616+ $arr = array();
 617+ } else {
 618+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
 619+ $stk = array(SERVICES_JSON_IN_OBJ);
 620+ $obj = array();
 621+ } else {
 622+ $stk = array(SERVICES_JSON_IN_OBJ);
 623+ $obj = new stdClass();
 624+ }
 625+ }
 626+
 627+ array_push($stk, array('what' => SERVICES_JSON_SLICE,
 628+ 'where' => 0,
 629+ 'delim' => false));
 630+
 631+ $chrs = substr($str, 1, -1);
 632+ $chrs = $this->reduce_string($chrs);
 633+
 634+ if ($chrs == '') {
 635+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
 636+ return $arr;
 637+
 638+ } else {
 639+ return $obj;
 640+
 641+ }
 642+ }
 643+
 644+ //print("\nparsing {$chrs}\n");
 645+
 646+ $strlen_chrs = strlen($chrs);
 647+
 648+ for ($c = 0; $c <= $strlen_chrs; ++$c) {
 649+
 650+ $top = end($stk);
 651+ $substr_chrs_c_2 = substr($chrs, $c, 2);
 652+
 653+ if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
 654+ // found a comma that is not inside a string, array, etc.,
 655+ // OR we've reached the end of the character list
 656+ $slice = substr($chrs, $top['where'], ($c - $top['where']));
 657+ array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
 658+ //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
 659+
 660+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
 661+ // we are in an array, so just push an element onto the stack
 662+ array_push($arr, $this->decode($slice));
 663+
 664+ } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
 665+ // we are in an object, so figure
 666+ // out the property name and set an
 667+ // element in an associative array,
 668+ // for now
 669+ $parts = array();
 670+
 671+ if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
 672+ // "name":value pair
 673+ $key = $this->decode($parts[1]);
 674+ $val = $this->decode($parts[2]);
 675+
 676+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
 677+ $obj[$key] = $val;
 678+ } else {
 679+ $obj->$key = $val;
 680+ }
 681+ } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
 682+ // name:value pair, where name is unquoted
 683+ $key = $parts[1];
 684+ $val = $this->decode($parts[2]);
 685+
 686+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
 687+ $obj[$key] = $val;
 688+ } else {
 689+ $obj->$key = $val;
 690+ }
 691+ }
 692+
 693+ }
 694+
 695+ } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
 696+ // found a quote, and we are not inside a string
 697+ array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
 698+ //print("Found start of string at {$c}\n");
 699+
 700+ } elseif (($chrs{$c} == $top['delim']) &&
 701+ ($top['what'] == SERVICES_JSON_IN_STR) &&
 702+ ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
 703+ // found a quote, we're in a string, and it's not escaped
 704+ // we know that it's not escaped becase there is _not_ an
 705+ // odd number of backslashes at the end of the string so far
 706+ array_pop($stk);
 707+ //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
 708+
 709+ } elseif (($chrs{$c} == '[') &&
 710+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
 711+ // found a left-bracket, and we are in an array, object, or slice
 712+ array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
 713+ //print("Found start of array at {$c}\n");
 714+
 715+ } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
 716+ // found a right-bracket, and we're in an array
 717+ array_pop($stk);
 718+ //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
 719+
 720+ } elseif (($chrs{$c} == '{') &&
 721+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
 722+ // found a left-brace, and we are in an array, object, or slice
 723+ array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
 724+ //print("Found start of object at {$c}\n");
 725+
 726+ } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
 727+ // found a right-brace, and we're in an object
 728+ array_pop($stk);
 729+ //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
 730+
 731+ } elseif (($substr_chrs_c_2 == '/*') &&
 732+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
 733+ // found a comment start, and we are in an array, object, or slice
 734+ array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
 735+ $c++;
 736+ //print("Found start of comment at {$c}\n");
 737+
 738+ } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
 739+ // found a comment end, and we're in one now
 740+ array_pop($stk);
 741+ $c++;
 742+
 743+ for ($i = $top['where']; $i <= $c; ++$i)
 744+ $chrs = substr_replace($chrs, ' ', $i, 1);
 745+
 746+ //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
 747+
 748+ }
 749+
 750+ }
 751+
 752+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
 753+ return $arr;
 754+
 755+ } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
 756+ return $obj;
 757+
 758+ }
 759+
 760+ }
 761+ }
 762+ }
 763+
 764+ /**
 765+ * @todo Ultimately, this should just call PEAR::isError()
 766+ */
 767+ function isError($data, $code = null)
 768+ {
 769+ if (class_exists('pear')) {
 770+ return PEAR::isError($data, $code);
 771+ } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
 772+ is_subclass_of($data, 'services_json_error'))) {
 773+ return true;
 774+ }
 775+
 776+ return false;
 777+ }
 778+}
 779+
 780+if (class_exists('PEAR_Error')) {
 781+
 782+ class Services_JSON_Error extends PEAR_Error
 783+ {
 784+ function Services_JSON_Error($message = 'unknown error', $code = null,
 785+ $mode = null, $options = null, $userinfo = null)
 786+ {
 787+ parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
 788+ }
 789+ }
 790+
 791+} else {
 792+
 793+ /**
 794+ * @todo Ultimately, this class shall be descended from PEAR_Error
 795+ */
 796+ class Services_JSON_Error
 797+ {
 798+ function Services_JSON_Error($message = 'unknown error', $code = null,
 799+ $mode = null, $options = null, $userinfo = null)
 800+ {
 801+
 802+ }
 803+ }
 804+
 805+}
 806+
 807+?>
Property changes on: trunk/extensions/WikiSync/pear/JSON.php
___________________________________________________________________
Added: svn:eol-style
1808 + native
Index: trunk/extensions/WikiSync/pear/LICENSE
@@ -0,0 +1,21 @@
 2+Redistribution and use in source and binary forms, with or without
 3+modification, are permitted provided that the following conditions are
 4+met:
 5+
 6+Redistributions of source code must retain the above copyright notice,
 7+this list of conditions and the following disclaimer.
 8+
 9+Redistributions in binary form must reproduce the above copyright
 10+notice, this list of conditions and the following disclaimer in the
 11+documentation and/or other materials provided with the distribution.
 12+
 13+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 14+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 15+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
 16+NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 17+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 18+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 19+USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 20+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 21+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 22+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Index: trunk/extensions/WikiSync/Snoopy/Snoopy.class.php
@@ -0,0 +1,1250 @@
 2+<?php
 3+
 4+/*************************************************
 5+
 6+Snoopy - the PHP net client
 7+Author: Monte Ohrt <monte@ispi.net>
 8+Copyright (c): 1999-2008 New Digital Group, all rights reserved
 9+Version: 1.2.4
 10+
 11+ * This library is free software; you can redistribute it and/or
 12+ * modify it under the terms of the GNU Lesser General Public
 13+ * License as published by the Free Software Foundation; either
 14+ * version 2.1 of the License, or (at your option) any later version.
 15+ *
 16+ * This library is distributed in the hope that it will be useful,
 17+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 18+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 19+ * Lesser General Public License for more details.
 20+ *
 21+ * You should have received a copy of the GNU Lesser General Public
 22+ * License along with this library; if not, write to the Free Software
 23+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 24+
 25+You may contact the author of Snoopy by e-mail at:
 26+monte@ohrt.com
 27+
 28+The latest version of Snoopy can be obtained from:
 29+http://snoopy.sourceforge.net/
 30+
 31+*************************************************/
 32+
 33+class Snoopy
 34+{
 35+ /**** Public variables ****/
 36+
 37+ /* user definable vars */
 38+
 39+ var $host = "www.php.net"; // host name we are connecting to
 40+ var $port = 80; // port we are connecting to
 41+ var $proxy_host = ""; // proxy host to use
 42+ var $proxy_port = ""; // proxy port to use
 43+ var $proxy_user = ""; // proxy user to use
 44+ var $proxy_pass = ""; // proxy password to use
 45+
 46+ var $agent = "Snoopy v1.2.4"; // agent we masquerade as
 47+ var $referer = ""; // referer info to pass
 48+ var $cookies = array(); // array of cookies to pass
 49+ // $cookies["username"]="joe";
 50+ var $rawheaders = array(); // array of raw headers to send
 51+ // $rawheaders["Content-type"]="text/html";
 52+
 53+ var $maxredirs = 5; // http redirection depth maximum. 0 = disallow
 54+ var $lastredirectaddr = ""; // contains address of last redirected address
 55+ var $offsiteok = true; // allows redirection off-site
 56+ var $maxframes = 0; // frame content depth maximum. 0 = disallow
 57+ var $expandlinks = true; // expand links to fully qualified URLs.
 58+ // this only applies to fetchlinks()
 59+ // submitlinks(), and submittext()
 60+ var $passcookies = true; // pass set cookies back through redirects
 61+ // NOTE: this currently does not respect
 62+ // dates, domains or paths.
 63+
 64+ var $user = ""; // user for http authentication
 65+ var $pass = ""; // password for http authentication
 66+
 67+ // http accept types
 68+ var $accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*";
 69+
 70+ var $results = ""; // where the content is put
 71+
 72+ var $error = ""; // error messages sent here
 73+ var $response_code = ""; // response code returned from server
 74+ var $headers = array(); // headers returned from server sent here
 75+ var $maxlength = 500000; // max return data length (body)
 76+ var $read_timeout = 0; // timeout on read operations, in seconds
 77+ // supported only since PHP 4 Beta 4
 78+ // set to 0 to disallow timeouts
 79+ var $timed_out = false; // if a read operation timed out
 80+ var $status = 0; // http request status
 81+
 82+ var $temp_dir = "/tmp"; // temporary directory that the webserver
 83+ // has permission to write to.
 84+ // under Windows, this should be C:\temp
 85+
 86+ var $curl_path = "/usr/local/bin/curl";
 87+ // Snoopy will use cURL for fetching
 88+ // SSL content if a full system path to
 89+ // the cURL binary is supplied here.
 90+ // set to false if you do not have
 91+ // cURL installed. See http://curl.haxx.se
 92+ // for details on installing cURL.
 93+ // Snoopy does *not* use the cURL
 94+ // library functions built into php,
 95+ // as these functions are not stable
 96+ // as of this Snoopy release.
 97+
 98+ /**** Private variables ****/
 99+
 100+ var $_maxlinelen = 4096; // max line length (headers)
 101+
 102+ var $_httpmethod = "GET"; // default http request method
 103+ var $_httpversion = "HTTP/1.0"; // default http request version
 104+ var $_submit_method = "POST"; // default submit method
 105+ var $_submit_type = "application/x-www-form-urlencoded"; // default submit type
 106+ var $_mime_boundary = ""; // MIME boundary for multipart/form-data submit type
 107+ var $_redirectaddr = false; // will be set if page fetched is a redirect
 108+ var $_redirectdepth = 0; // increments on an http redirect
 109+ var $_frameurls = array(); // frame src urls
 110+ var $_framedepth = 0; // increments on frame depth
 111+
 112+ var $_isproxy = false; // set if using a proxy server
 113+ var $_fp_timeout = 30; // timeout for socket connection
 114+
 115+/*======================================================================*\
 116+ Function: fetch
 117+ Purpose: fetch the contents of a web page
 118+ (and possibly other protocols in the
 119+ future like ftp, nntp, gopher, etc.)
 120+ Input: $URI the location of the page to fetch
 121+ Output: $this->results the output text from the fetch
 122+\*======================================================================*/
 123+
 124+ function fetch($URI)
 125+ {
 126+
 127+ //preg_match("|^([^:]+)://([^:/]+)(:[\d]+)*(.*)|",$URI,$URI_PARTS);
 128+ $URI_PARTS = parse_url($URI);
 129+ if (!empty($URI_PARTS["user"]))
 130+ $this->user = $URI_PARTS["user"];
 131+ if (!empty($URI_PARTS["pass"]))
 132+ $this->pass = $URI_PARTS["pass"];
 133+ if (empty($URI_PARTS["query"]))
 134+ $URI_PARTS["query"] = '';
 135+ if (empty($URI_PARTS["path"]))
 136+ $URI_PARTS["path"] = '';
 137+
 138+ switch(strtolower($URI_PARTS["scheme"]))
 139+ {
 140+ case "http":
 141+ $this->host = $URI_PARTS["host"];
 142+ if(!empty($URI_PARTS["port"]))
 143+ $this->port = $URI_PARTS["port"];
 144+ if($this->_connect($fp))
 145+ {
 146+ if($this->_isproxy)
 147+ {
 148+ // using proxy, send entire URI
 149+ $this->_httprequest($URI,$fp,$URI,$this->_httpmethod);
 150+ }
 151+ else
 152+ {
 153+ $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : "");
 154+ // no proxy, send only the path
 155+ $this->_httprequest($path, $fp, $URI, $this->_httpmethod);
 156+ }
 157+
 158+ $this->_disconnect($fp);
 159+
 160+ if($this->_redirectaddr)
 161+ {
 162+ /* url was redirected, check if we've hit the max depth */
 163+ if($this->maxredirs > $this->_redirectdepth)
 164+ {
 165+ // only follow redirect if it's on this site, or offsiteok is true
 166+ if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok)
 167+ {
 168+ /* follow the redirect */
 169+ $this->_redirectdepth++;
 170+ $this->lastredirectaddr=$this->_redirectaddr;
 171+ $this->fetch($this->_redirectaddr);
 172+ }
 173+ }
 174+ }
 175+
 176+ if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0)
 177+ {
 178+ $frameurls = $this->_frameurls;
 179+ $this->_frameurls = array();
 180+
 181+ while(list(,$frameurl) = each($frameurls))
 182+ {
 183+ if($this->_framedepth < $this->maxframes)
 184+ {
 185+ $this->fetch($frameurl);
 186+ $this->_framedepth++;
 187+ }
 188+ else
 189+ break;
 190+ }
 191+ }
 192+ }
 193+ else
 194+ {
 195+ return false;
 196+ }
 197+ return true;
 198+ break;
 199+ case "https":
 200+ if(!$this->curl_path)
 201+ return false;
 202+ if(function_exists("is_executable"))
 203+ if (!is_executable($this->curl_path))
 204+ return false;
 205+ $this->host = $URI_PARTS["host"];
 206+ if(!empty($URI_PARTS["port"]))
 207+ $this->port = $URI_PARTS["port"];
 208+ if($this->_isproxy)
 209+ {
 210+ // using proxy, send entire URI
 211+ $this->_httpsrequest($URI,$URI,$this->_httpmethod);
 212+ }
 213+ else
 214+ {
 215+ $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : "");
 216+ // no proxy, send only the path
 217+ $this->_httpsrequest($path, $URI, $this->_httpmethod);
 218+ }
 219+
 220+ if($this->_redirectaddr)
 221+ {
 222+ /* url was redirected, check if we've hit the max depth */
 223+ if($this->maxredirs > $this->_redirectdepth)
 224+ {
 225+ // only follow redirect if it's on this site, or offsiteok is true
 226+ if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok)
 227+ {
 228+ /* follow the redirect */
 229+ $this->_redirectdepth++;
 230+ $this->lastredirectaddr=$this->_redirectaddr;
 231+ $this->fetch($this->_redirectaddr);
 232+ }
 233+ }
 234+ }
 235+
 236+ if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0)
 237+ {
 238+ $frameurls = $this->_frameurls;
 239+ $this->_frameurls = array();
 240+
 241+ while(list(,$frameurl) = each($frameurls))
 242+ {
 243+ if($this->_framedepth < $this->maxframes)
 244+ {
 245+ $this->fetch($frameurl);
 246+ $this->_framedepth++;
 247+ }
 248+ else
 249+ break;
 250+ }
 251+ }
 252+ return true;
 253+ break;
 254+ default:
 255+ // not a valid protocol
 256+ $this->error = 'Invalid protocol "'.$URI_PARTS["scheme"].'"\n';
 257+ return false;
 258+ break;
 259+ }
 260+ return true;
 261+ }
 262+
 263+/*======================================================================*\
 264+ Function: submit
 265+ Purpose: submit an http form
 266+ Input: $URI the location to post the data
 267+ $formvars the formvars to use.
 268+ format: $formvars["var"] = "val";
 269+ $formfiles an array of files to submit
 270+ format: $formfiles["var"] = "/dir/filename.ext";
 271+ Output: $this->results the text output from the post
 272+\*======================================================================*/
 273+
 274+ function submit($URI, $formvars="", $formfiles="")
 275+ {
 276+ unset($postdata);
 277+
 278+ $postdata = $this->_prepare_post_body($formvars, $formfiles);
 279+
 280+ $URI_PARTS = parse_url($URI);
 281+ if (!empty($URI_PARTS["user"]))
 282+ $this->user = $URI_PARTS["user"];
 283+ if (!empty($URI_PARTS["pass"]))
 284+ $this->pass = $URI_PARTS["pass"];
 285+ if (empty($URI_PARTS["query"]))
 286+ $URI_PARTS["query"] = '';
 287+ if (empty($URI_PARTS["path"]))
 288+ $URI_PARTS["path"] = '';
 289+
 290+ switch(strtolower($URI_PARTS["scheme"]))
 291+ {
 292+ case "http":
 293+ $this->host = $URI_PARTS["host"];
 294+ if(!empty($URI_PARTS["port"]))
 295+ $this->port = $URI_PARTS["port"];
 296+ if($this->_connect($fp))
 297+ {
 298+ if($this->_isproxy)
 299+ {
 300+ // using proxy, send entire URI
 301+ $this->_httprequest($URI,$fp,$URI,$this->_submit_method,$this->_submit_type,$postdata);
 302+ }
 303+ else
 304+ {
 305+ $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : "");
 306+ // no proxy, send only the path
 307+ $this->_httprequest($path, $fp, $URI, $this->_submit_method, $this->_submit_type, $postdata);
 308+ }
 309+
 310+ $this->_disconnect($fp);
 311+
 312+ if($this->_redirectaddr)
 313+ {
 314+ /* url was redirected, check if we've hit the max depth */
 315+ if($this->maxredirs > $this->_redirectdepth)
 316+ {
 317+ if(!preg_match("|^".$URI_PARTS["scheme"]."://|", $this->_redirectaddr))
 318+ $this->_redirectaddr = $this->_expandlinks($this->_redirectaddr,$URI_PARTS["scheme"]."://".$URI_PARTS["host"]);
 319+
 320+ // only follow redirect if it's on this site, or offsiteok is true
 321+ if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok)
 322+ {
 323+ /* follow the redirect */
 324+ $this->_redirectdepth++;
 325+ $this->lastredirectaddr=$this->_redirectaddr;
 326+ if( strpos( $this->_redirectaddr, "?" ) > 0 )
 327+ $this->fetch($this->_redirectaddr); // the redirect has changed the request method from post to get
 328+ else
 329+ $this->submit($this->_redirectaddr,$formvars, $formfiles);
 330+ }
 331+ }
 332+ }
 333+
 334+ if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0)
 335+ {
 336+ $frameurls = $this->_frameurls;
 337+ $this->_frameurls = array();
 338+
 339+ while(list(,$frameurl) = each($frameurls))
 340+ {
 341+ if($this->_framedepth < $this->maxframes)
 342+ {
 343+ $this->fetch($frameurl);
 344+ $this->_framedepth++;
 345+ }
 346+ else
 347+ break;
 348+ }
 349+ }
 350+
 351+ }
 352+ else
 353+ {
 354+ return false;
 355+ }
 356+ return true;
 357+ break;
 358+ case "https":
 359+ if(!$this->curl_path)
 360+ return false;
 361+ if(function_exists("is_executable"))
 362+ if (!is_executable($this->curl_path))
 363+ return false;
 364+ $this->host = $URI_PARTS["host"];
 365+ if(!empty($URI_PARTS["port"]))
 366+ $this->port = $URI_PARTS["port"];
 367+ if($this->_isproxy)
 368+ {
 369+ // using proxy, send entire URI
 370+ $this->_httpsrequest($URI, $URI, $this->_submit_method, $this->_submit_type, $postdata);
 371+ }
 372+ else
 373+ {
 374+ $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : "");
 375+ // no proxy, send only the path
 376+ $this->_httpsrequest($path, $URI, $this->_submit_method, $this->_submit_type, $postdata);
 377+ }
 378+
 379+ if($this->_redirectaddr)
 380+ {
 381+ /* url was redirected, check if we've hit the max depth */
 382+ if($this->maxredirs > $this->_redirectdepth)
 383+ {
 384+ if(!preg_match("|^".$URI_PARTS["scheme"]."://|", $this->_redirectaddr))
 385+ $this->_redirectaddr = $this->_expandlinks($this->_redirectaddr,$URI_PARTS["scheme"]."://".$URI_PARTS["host"]);
 386+
 387+ // only follow redirect if it's on this site, or offsiteok is true
 388+ if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok)
 389+ {
 390+ /* follow the redirect */
 391+ $this->_redirectdepth++;
 392+ $this->lastredirectaddr=$this->_redirectaddr;
 393+ if( strpos( $this->_redirectaddr, "?" ) > 0 )
 394+ $this->fetch($this->_redirectaddr); // the redirect has changed the request method from post to get
 395+ else
 396+ $this->submit($this->_redirectaddr,$formvars, $formfiles);
 397+ }
 398+ }
 399+ }
 400+
 401+ if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0)
 402+ {
 403+ $frameurls = $this->_frameurls;
 404+ $this->_frameurls = array();
 405+
 406+ while(list(,$frameurl) = each($frameurls))
 407+ {
 408+ if($this->_framedepth < $this->maxframes)
 409+ {
 410+ $this->fetch($frameurl);
 411+ $this->_framedepth++;
 412+ }
 413+ else
 414+ break;
 415+ }
 416+ }
 417+ return true;
 418+ break;
 419+
 420+ default:
 421+ // not a valid protocol
 422+ $this->error = 'Invalid protocol "'.$URI_PARTS["scheme"].'"\n';
 423+ return false;
 424+ break;
 425+ }
 426+ return true;
 427+ }
 428+
 429+/*======================================================================*\
 430+ Function: fetchlinks
 431+ Purpose: fetch the links from a web page
 432+ Input: $URI where you are fetching from
 433+ Output: $this->results an array of the URLs
 434+\*======================================================================*/
 435+
 436+ function fetchlinks($URI)
 437+ {
 438+ if ($this->fetch($URI))
 439+ {
 440+ if($this->lastredirectaddr)
 441+ $URI = $this->lastredirectaddr;
 442+ if(is_array($this->results))
 443+ {
 444+ for($x=0;$x<count($this->results);$x++)
 445+ $this->results[$x] = $this->_striplinks($this->results[$x]);
 446+ }
 447+ else
 448+ $this->results = $this->_striplinks($this->results);
 449+
 450+ if($this->expandlinks)
 451+ $this->results = $this->_expandlinks($this->results, $URI);
 452+ return true;
 453+ }
 454+ else
 455+ return false;
 456+ }
 457+
 458+/*======================================================================*\
 459+ Function: fetchform
 460+ Purpose: fetch the form elements from a web page
 461+ Input: $URI where you are fetching from
 462+ Output: $this->results the resulting html form
 463+\*======================================================================*/
 464+
 465+ function fetchform($URI)
 466+ {
 467+
 468+ if ($this->fetch($URI))
 469+ {
 470+
 471+ if(is_array($this->results))
 472+ {
 473+ for($x=0;$x<count($this->results);$x++)
 474+ $this->results[$x] = $this->_stripform($this->results[$x]);
 475+ }
 476+ else
 477+ $this->results = $this->_stripform($this->results);
 478+
 479+ return true;
 480+ }
 481+ else
 482+ return false;
 483+ }
 484+
 485+
 486+/*======================================================================*\
 487+ Function: fetchtext
 488+ Purpose: fetch the text from a web page, stripping the links
 489+ Input: $URI where you are fetching from
 490+ Output: $this->results the text from the web page
 491+\*======================================================================*/
 492+
 493+ function fetchtext($URI)
 494+ {
 495+ if($this->fetch($URI))
 496+ {
 497+ if(is_array($this->results))
 498+ {
 499+ for($x=0;$x<count($this->results);$x++)
 500+ $this->results[$x] = $this->_striptext($this->results[$x]);
 501+ }
 502+ else
 503+ $this->results = $this->_striptext($this->results);
 504+ return true;
 505+ }
 506+ else
 507+ return false;
 508+ }
 509+
 510+/*======================================================================*\
 511+ Function: submitlinks
 512+ Purpose: grab links from a form submission
 513+ Input: $URI where you are submitting from
 514+ Output: $this->results an array of the links from the post
 515+\*======================================================================*/
 516+
 517+ function submitlinks($URI, $formvars="", $formfiles="")
 518+ {
 519+ if($this->submit($URI,$formvars, $formfiles))
 520+ {
 521+ if($this->lastredirectaddr)
 522+ $URI = $this->lastredirectaddr;
 523+ if(is_array($this->results))
 524+ {
 525+ for($x=0;$x<count($this->results);$x++)
 526+ {
 527+ $this->results[$x] = $this->_striplinks($this->results[$x]);
 528+ if($this->expandlinks)
 529+ $this->results[$x] = $this->_expandlinks($this->results[$x],$URI);
 530+ }
 531+ }
 532+ else
 533+ {
 534+ $this->results = $this->_striplinks($this->results);
 535+ if($this->expandlinks)
 536+ $this->results = $this->_expandlinks($this->results,$URI);
 537+ }
 538+ return true;
 539+ }
 540+ else
 541+ return false;
 542+ }
 543+
 544+/*======================================================================*\
 545+ Function: submittext
 546+ Purpose: grab text from a form submission
 547+ Input: $URI where you are submitting from
 548+ Output: $this->results the text from the web page
 549+\*======================================================================*/
 550+
 551+ function submittext($URI, $formvars = "", $formfiles = "")
 552+ {
 553+ if($this->submit($URI,$formvars, $formfiles))
 554+ {
 555+ if($this->lastredirectaddr)
 556+ $URI = $this->lastredirectaddr;
 557+ if(is_array($this->results))
 558+ {
 559+ for($x=0;$x<count($this->results);$x++)
 560+ {
 561+ $this->results[$x] = $this->_striptext($this->results[$x]);
 562+ if($this->expandlinks)
 563+ $this->results[$x] = $this->_expandlinks($this->results[$x],$URI);
 564+ }
 565+ }
 566+ else
 567+ {
 568+ $this->results = $this->_striptext($this->results);
 569+ if($this->expandlinks)
 570+ $this->results = $this->_expandlinks($this->results,$URI);
 571+ }
 572+ return true;
 573+ }
 574+ else
 575+ return false;
 576+ }
 577+
 578+
 579+
 580+/*======================================================================*\
 581+ Function: set_submit_multipart
 582+ Purpose: Set the form submission content type to
 583+ multipart/form-data
 584+\*======================================================================*/
 585+ function set_submit_multipart()
 586+ {
 587+ $this->_submit_type = "multipart/form-data";
 588+ }
 589+
 590+
 591+/*======================================================================*\
 592+ Function: set_submit_normal
 593+ Purpose: Set the form submission content type to
 594+ application/x-www-form-urlencoded
 595+\*======================================================================*/
 596+ function set_submit_normal()
 597+ {
 598+ $this->_submit_type = "application/x-www-form-urlencoded";
 599+ }
 600+
 601+
 602+
 603+
 604+/*======================================================================*\
 605+ Private functions
 606+\*======================================================================*/
 607+
 608+
 609+/*======================================================================*\
 610+ Function: _striplinks
 611+ Purpose: strip the hyperlinks from an html document
 612+ Input: $document document to strip.
 613+ Output: $match an array of the links
 614+\*======================================================================*/
 615+
 616+ function _striplinks($document)
 617+ {
 618+ preg_match_all("'<\s*a\s.*?href\s*=\s* # find <a href=
 619+ ([\"\'])? # find single or double quote
 620+ (?(1) (.*?)\\1 | ([^\s\>]+)) # if quote found, match up to next matching
 621+ # quote, otherwise match up to next space
 622+ 'isx",$document,$links);
 623+
 624+
 625+ // catenate the non-empty matches from the conditional subpattern
 626+
 627+ while(list($key,$val) = each($links[2]))
 628+ {
 629+ if(!empty($val))
 630+ $match[] = $val;
 631+ }
 632+
 633+ while(list($key,$val) = each($links[3]))
 634+ {
 635+ if(!empty($val))
 636+ $match[] = $val;
 637+ }
 638+
 639+ // return the links
 640+ return $match;
 641+ }
 642+
 643+/*======================================================================*\
 644+ Function: _stripform
 645+ Purpose: strip the form elements from an html document
 646+ Input: $document document to strip.
 647+ Output: $match an array of the links
 648+\*======================================================================*/
 649+
 650+ function _stripform($document)
 651+ {
 652+ preg_match_all("'<\/?(FORM|INPUT|SELECT|TEXTAREA|(OPTION))[^<>]*>(?(2)(.*(?=<\/?(option|select)[^<>]*>[\r\n]*)|(?=[\r\n]*))|(?=[\r\n]*))'Usi",$document,$elements);
 653+
 654+ // catenate the matches
 655+ $match = implode("\r\n",$elements[0]);
 656+
 657+ // return the links
 658+ return $match;
 659+ }
 660+
 661+
 662+
 663+/*======================================================================*\
 664+ Function: _striptext
 665+ Purpose: strip the text from an html document
 666+ Input: $document document to strip.
 667+ Output: $text the resulting text
 668+\*======================================================================*/
 669+
 670+ function _striptext($document)
 671+ {
 672+
 673+ // I didn't use preg eval (//e) since that is only available in PHP 4.0.
 674+ // so, list your entities one by one here. I included some of the
 675+ // more common ones.
 676+
 677+ $search = array("'<script[^>]*?>.*?</script>'si", // strip out javascript
 678+ "'<[\/\!]*?[^<>]*?>'si", // strip out html tags
 679+ "'([\r\n])[\s]+'", // strip out white space
 680+ "'&(quot|#34|#034|#x22);'i", // replace html entities
 681+ "'&(amp|#38|#038|#x26);'i", // added hexadecimal values
 682+ "'&(lt|#60|#060|#x3c);'i",
 683+ "'&(gt|#62|#062|#x3e);'i",
 684+ "'&(nbsp|#160|#xa0);'i",
 685+ "'&(iexcl|#161);'i",
 686+ "'&(cent|#162);'i",
 687+ "'&(pound|#163);'i",
 688+ "'&(copy|#169);'i",
 689+ "'&(reg|#174);'i",
 690+ "'&(deg|#176);'i",
 691+ "'&(#39|#039|#x27);'",
 692+ "'&(euro|#8364);'i", // europe
 693+ "'&a(uml|UML);'", // german
 694+ "'&o(uml|UML);'",
 695+ "'&u(uml|UML);'",
 696+ "'&A(uml|UML);'",
 697+ "'&O(uml|UML);'",
 698+ "'&U(uml|UML);'",
 699+ "'&szlig;'i",
 700+ );
 701+ $replace = array( "",
 702+ "",
 703+ "\\1",
 704+ "\"",
 705+ "&",
 706+ "<",
 707+ ">",
 708+ " ",
 709+ chr(161),
 710+ chr(162),
 711+ chr(163),
 712+ chr(169),
 713+ chr(174),
 714+ chr(176),
 715+ chr(39),
 716+ chr(128),
 717+ "�",
 718+ "�",
 719+ "�",
 720+ "�",
 721+ "�",
 722+ "�",
 723+ "�",
 724+ );
 725+
 726+ $text = preg_replace($search,$replace,$document);
 727+
 728+ return $text;
 729+ }
 730+
 731+/*======================================================================*\
 732+ Function: _expandlinks
 733+ Purpose: expand each link into a fully qualified URL
 734+ Input: $links the links to qualify
 735+ $URI the full URI to get the base from
 736+ Output: $expandedLinks the expanded links
 737+\*======================================================================*/
 738+
 739+ function _expandlinks($links,$URI)
 740+ {
 741+
 742+ preg_match("/^[^\?]+/",$URI,$match);
 743+
 744+ $match = preg_replace("|/[^\/\.]+\.[^\/\.]+$|","",$match[0]);
 745+ $match = preg_replace("|/$|","",$match);
 746+ $match_part = parse_url($match);
 747+ $match_root =
 748+ $match_part["scheme"]."://".$match_part["host"];
 749+
 750+ $search = array( "|^http://".preg_quote($this->host)."|i",
 751+ "|^(\/)|i",
 752+ "|^(?!http://)(?!mailto:)|i",
 753+ "|/\./|",
 754+ "|/[^\/]+/\.\./|"
 755+ );
 756+
 757+ $replace = array( "",
 758+ $match_root."/",
 759+ $match."/",
 760+ "/",
 761+ "/"
 762+ );
 763+
 764+ $expandedLinks = preg_replace($search,$replace,$links);
 765+
 766+ return $expandedLinks;
 767+ }
 768+
 769+/*======================================================================*\
 770+ Function: _httprequest
 771+ Purpose: go get the http data from the server
 772+ Input: $url the url to fetch
 773+ $fp the current open file pointer
 774+ $URI the full URI
 775+ $body body contents to send if any (POST)
 776+ Output:
 777+\*======================================================================*/
 778+
 779+ function _httprequest($url,$fp,$URI,$http_method,$content_type="",$body="")
 780+ {
 781+ $cookie_headers = '';
 782+ if($this->passcookies && $this->_redirectaddr)
 783+ $this->setcookies();
 784+
 785+ $URI_PARTS = parse_url($URI);
 786+ if(empty($url))
 787+ $url = "/";
 788+ $headers = $http_method." ".$url." ".$this->_httpversion."\r\n";
 789+ if(!empty($this->agent))
 790+ $headers .= "User-Agent: ".$this->agent."\r\n";
 791+ if(!empty($this->host) && !isset($this->rawheaders['Host'])) {
 792+ $headers .= "Host: ".$this->host;
 793+ if(!empty($this->port))
 794+ $headers .= ":".$this->port;
 795+ $headers .= "\r\n";
 796+ }
 797+ if(!empty($this->accept))
 798+ $headers .= "Accept: ".$this->accept."\r\n";
 799+ if(!empty($this->referer))
 800+ $headers .= "Referer: ".$this->referer."\r\n";
 801+ if(!empty($this->cookies))
 802+ {
 803+ if(!is_array($this->cookies))
 804+ $this->cookies = (array)$this->cookies;
 805+
 806+ reset($this->cookies);
 807+ if ( count($this->cookies) > 0 ) {
 808+ $cookie_headers .= 'Cookie: ';
 809+ foreach ( $this->cookies as $cookieKey => $cookieVal ) {
 810+ $cookie_headers .= $cookieKey."=".urlencode($cookieVal)."; ";
 811+ }
 812+ $headers .= substr($cookie_headers,0,-2) . "\r\n";
 813+ }
 814+ }
 815+ if(!empty($this->rawheaders))
 816+ {
 817+ if(!is_array($this->rawheaders))
 818+ $this->rawheaders = (array)$this->rawheaders;
 819+ while(list($headerKey,$headerVal) = each($this->rawheaders))
 820+ $headers .= $headerKey.": ".$headerVal."\r\n";
 821+ }
 822+ if(!empty($content_type)) {
 823+ $headers .= "Content-type: $content_type";
 824+ if ($content_type == "multipart/form-data")
 825+ $headers .= "; boundary=".$this->_mime_boundary;
 826+ $headers .= "\r\n";
 827+ }
 828+ if(!empty($body))
 829+ $headers .= "Content-length: ".strlen($body)."\r\n";
 830+ if(!empty($this->user) || !empty($this->pass))
 831+ $headers .= "Authorization: Basic ".base64_encode($this->user.":".$this->pass)."\r\n";
 832+
 833+ //add proxy auth headers
 834+ if(!empty($this->proxy_user))
 835+ $headers .= 'Proxy-Authorization: ' . 'Basic ' . base64_encode($this->proxy_user . ':' . $this->proxy_pass)."\r\n";
 836+
 837+
 838+ $headers .= "\r\n";
 839+
 840+ // set the read timeout if needed
 841+ if ($this->read_timeout > 0)
 842+ socket_set_timeout($fp, $this->read_timeout);
 843+ $this->timed_out = false;
 844+
 845+ @fwrite($fp,$headers.$body,strlen($headers.$body));
 846+
 847+ $this->_redirectaddr = false;
 848+ unset($this->headers);
 849+
 850+ while($currentHeader = @fgets($fp,$this->_maxlinelen))
 851+ {
 852+ if ($this->read_timeout > 0 && $this->_check_timeout($fp))
 853+ {
 854+ $this->status=-100;
 855+ return false;
 856+ }
 857+
 858+ if($currentHeader == "\r\n")
 859+ break;
 860+
 861+ // if a header begins with Location: or URI:, set the redirect
 862+ if(preg_match("/^(Location:|URI:)/i",$currentHeader))
 863+ {
 864+ // get URL portion of the redirect
 865+ preg_match("/^(Location:|URI:)[ ]+(.*)/i",chop($currentHeader),$matches);
 866+ // look for :// in the Location header to see if hostname is included
 867+ if(!preg_match("|\:\/\/|",$matches[2]))
 868+ {
 869+ // no host in the path, so prepend
 870+ $this->_redirectaddr = $URI_PARTS["scheme"]."://".$this->host.":".$this->port;
 871+ // eliminate double slash
 872+ if(!preg_match("|^/|",$matches[2]))
 873+ $this->_redirectaddr .= "/".$matches[2];
 874+ else
 875+ $this->_redirectaddr .= $matches[2];
 876+ }
 877+ else
 878+ $this->_redirectaddr = $matches[2];
 879+ }
 880+
 881+ if(preg_match("|^HTTP/|",$currentHeader))
 882+ {
 883+ if(preg_match("|^HTTP/[^\s]*\s(.*?)\s|",$currentHeader, $status))
 884+ {
 885+ $this->status= $status[1];
 886+ }
 887+ $this->response_code = $currentHeader;
 888+ }
 889+
 890+ $this->headers[] = $currentHeader;
 891+ }
 892+
 893+ $results = '';
 894+ do {
 895+ $_data = @fread($fp, $this->maxlength);
 896+ if (strlen($_data) == 0) {
 897+ break;
 898+ }
 899+ $results .= $_data;
 900+ } while(true);
 901+
 902+ if ($this->read_timeout > 0 && $this->_check_timeout($fp))
 903+ {
 904+ $this->status=-100;
 905+ return false;
 906+ }
 907+
 908+ // check if there is a a redirect meta tag
 909+
 910+ if(preg_match("'<meta[\s]*http-equiv[^>]*?content[\s]*=[\s]*[\"\']?\d+;[\s]*URL[\s]*=[\s]*([^\"\']*?)[\"\']?>'i",$results,$match))
 911+
 912+ {
 913+ $this->_redirectaddr = $this->_expandlinks($match[1],$URI);
 914+ }
 915+
 916+ // have we hit our frame depth and is there frame src to fetch?
 917+ if(($this->_framedepth < $this->maxframes) && preg_match_all("'<frame\s+.*src[\s]*=[\'\"]?([^\'\"\>]+)'i",$results,$match))
 918+ {
 919+ $this->results[] = $results;
 920+ for($x=0; $x<count($match[1]); $x++)
 921+ $this->_frameurls[] = $this->_expandlinks($match[1][$x],$URI_PARTS["scheme"]."://".$this->host);
 922+ }
 923+ // have we already fetched framed content?
 924+ elseif(is_array($this->results))
 925+ $this->results[] = $results;
 926+ // no framed content
 927+ else
 928+ $this->results = $results;
 929+
 930+ return true;
 931+ }
 932+
 933+/*======================================================================*\
 934+ Function: _httpsrequest
 935+ Purpose: go get the https data from the server using curl
 936+ Input: $url the url to fetch
 937+ $URI the full URI
 938+ $body body contents to send if any (POST)
 939+ Output:
 940+\*======================================================================*/
 941+
 942+ function _httpsrequest($url,$URI,$http_method,$content_type="",$body="")
 943+ {
 944+ if($this->passcookies && $this->_redirectaddr)
 945+ $this->setcookies();
 946+
 947+ $headers = array();
 948+
 949+ $URI_PARTS = parse_url($URI);
 950+ if(empty($url))
 951+ $url = "/";
 952+ // GET ... header not needed for curl
 953+ //$headers[] = $http_method." ".$url." ".$this->_httpversion;
 954+ if(!empty($this->agent))
 955+ $headers[] = "User-Agent: ".$this->agent;
 956+ if(!empty($this->host))
 957+ if(!empty($this->port))
 958+ $headers[] = "Host: ".$this->host.":".$this->port;
 959+ else
 960+ $headers[] = "Host: ".$this->host;
 961+ if(!empty($this->accept))
 962+ $headers[] = "Accept: ".$this->accept;
 963+ if(!empty($this->referer))
 964+ $headers[] = "Referer: ".$this->referer;
 965+ if(!empty($this->cookies))
 966+ {
 967+ if(!is_array($this->cookies))
 968+ $this->cookies = (array)$this->cookies;
 969+
 970+ reset($this->cookies);
 971+ if ( count($this->cookies) > 0 ) {
 972+ $cookie_str = 'Cookie: ';
 973+ foreach ( $this->cookies as $cookieKey => $cookieVal ) {
 974+ $cookie_str .= $cookieKey."=".urlencode($cookieVal)."; ";
 975+ }
 976+ $headers[] = substr($cookie_str,0,-2);
 977+ }
 978+ }
 979+ if(!empty($this->rawheaders))
 980+ {
 981+ if(!is_array($this->rawheaders))
 982+ $this->rawheaders = (array)$this->rawheaders;
 983+ while(list($headerKey,$headerVal) = each($this->rawheaders))
 984+ $headers[] = $headerKey.": ".$headerVal;
 985+ }
 986+ if(!empty($content_type)) {
 987+ if ($content_type == "multipart/form-data")
 988+ $headers[] = "Content-type: $content_type; boundary=".$this->_mime_boundary;
 989+ else
 990+ $headers[] = "Content-type: $content_type";
 991+ }
 992+ if(!empty($body))
 993+ $headers[] = "Content-length: ".strlen($body);
 994+ if(!empty($this->user) || !empty($this->pass))
 995+ $headers[] = "Authorization: BASIC ".base64_encode($this->user.":".$this->pass);
 996+
 997+ for($curr_header = 0; $curr_header < count($headers); $curr_header++) {
 998+ $safer_header = strtr( $headers[$curr_header], "\"", " " );
 999+ $cmdline_params .= " -H \"".$safer_header."\"";
 1000+ }
 1001+
 1002+ if(!empty($body))
 1003+ $cmdline_params .= " -d \"$body\"";
 1004+
 1005+ if($this->read_timeout > 0)
 1006+ $cmdline_params .= " -m ".$this->read_timeout;
 1007+
 1008+ $headerfile = tempnam($temp_dir, "sno");
 1009+
 1010+ exec($this->curl_path." -k -D \"$headerfile\"".$cmdline_params." \"".escapeshellcmd($URI)."\"",$results,$return);
 1011+
 1012+ if($return)
 1013+ {
 1014+ $this->error = "Error: cURL could not retrieve the document, error $return.";
 1015+ return false;
 1016+ }
 1017+
 1018+
 1019+ $results = implode("\r\n",$results);
 1020+
 1021+ $result_headers = file("$headerfile");
 1022+
 1023+ $this->_redirectaddr = false;
 1024+ unset($this->headers);
 1025+
 1026+ for($currentHeader = 0; $currentHeader < count($result_headers); $currentHeader++)
 1027+ {
 1028+
 1029+ // if a header begins with Location: or URI:, set the redirect
 1030+ if(preg_match("/^(Location: |URI: )/i",$result_headers[$currentHeader]))
 1031+ {
 1032+ // get URL portion of the redirect
 1033+ preg_match("/^(Location: |URI:)\s+(.*)/",chop($result_headers[$currentHeader]),$matches);
 1034+ // look for :// in the Location header to see if hostname is included
 1035+ if(!preg_match("|\:\/\/|",$matches[2]))
 1036+ {
 1037+ // no host in the path, so prepend
 1038+ $this->_redirectaddr = $URI_PARTS["scheme"]."://".$this->host.":".$this->port;
 1039+ // eliminate double slash
 1040+ if(!preg_match("|^/|",$matches[2]))
 1041+ $this->_redirectaddr .= "/".$matches[2];
 1042+ else
 1043+ $this->_redirectaddr .= $matches[2];
 1044+ }
 1045+ else
 1046+ $this->_redirectaddr = $matches[2];
 1047+ }
 1048+
 1049+ if(preg_match("|^HTTP/|",$result_headers[$currentHeader]))
 1050+ $this->response_code = $result_headers[$currentHeader];
 1051+
 1052+ $this->headers[] = $result_headers[$currentHeader];
 1053+ }
 1054+
 1055+ // check if there is a a redirect meta tag
 1056+
 1057+ if(preg_match("'<meta[\s]*http-equiv[^>]*?content[\s]*=[\s]*[\"\']?\d+;[\s]*URL[\s]*=[\s]*([^\"\']*?)[\"\']?>'i",$results,$match))
 1058+ {
 1059+ $this->_redirectaddr = $this->_expandlinks($match[1],$URI);
 1060+ }
 1061+
 1062+ // have we hit our frame depth and is there frame src to fetch?
 1063+ if(($this->_framedepth < $this->maxframes) && preg_match_all("'<frame\s+.*src[\s]*=[\'\"]?([^\'\"\>]+)'i",$results,$match))
 1064+ {
 1065+ $this->results[] = $results;
 1066+ for($x=0; $x<count($match[1]); $x++)
 1067+ $this->_frameurls[] = $this->_expandlinks($match[1][$x],$URI_PARTS["scheme"]."://".$this->host);
 1068+ }
 1069+ // have we already fetched framed content?
 1070+ elseif(is_array($this->results))
 1071+ $this->results[] = $results;
 1072+ // no framed content
 1073+ else
 1074+ $this->results = $results;
 1075+
 1076+ unlink("$headerfile");
 1077+
 1078+ return true;
 1079+ }
 1080+
 1081+/*======================================================================*\
 1082+ Function: setcookies()
 1083+ Purpose: set cookies for a redirection
 1084+\*======================================================================*/
 1085+
 1086+ function setcookies()
 1087+ {
 1088+ for($x=0; $x<count($this->headers); $x++)
 1089+ {
 1090+ if(preg_match('/^set-cookie:[\s]+([^=]+)=([^;]+)/i', $this->headers[$x],$match))
 1091+ $this->cookies[$match[1]] = urldecode($match[2]);
 1092+ }
 1093+ }
 1094+
 1095+
 1096+/*======================================================================*\
 1097+ Function: _check_timeout
 1098+ Purpose: checks whether timeout has occurred
 1099+ Input: $fp file pointer
 1100+\*======================================================================*/
 1101+
 1102+ function _check_timeout($fp)
 1103+ {
 1104+ if ($this->read_timeout > 0) {
 1105+ $fp_status = @socket_get_status($fp);
 1106+ if ($fp_status["timed_out"]) {
 1107+ $this->timed_out = true;
 1108+ return true;
 1109+ }
 1110+ }
 1111+ return false;
 1112+ }
 1113+
 1114+/*======================================================================*\
 1115+ Function: _connect
 1116+ Purpose: make a socket connection
 1117+ Input: $fp file pointer
 1118+\*======================================================================*/
 1119+
 1120+ function _connect(&$fp)
 1121+ {
 1122+ if(!empty($this->proxy_host) && !empty($this->proxy_port))
 1123+ {
 1124+ $this->_isproxy = true;
 1125+
 1126+ $host = $this->proxy_host;
 1127+ $port = $this->proxy_port;
 1128+ }
 1129+ else
 1130+ {
 1131+ $host = $this->host;
 1132+ $port = $this->port;
 1133+ }
 1134+
 1135+ $this->status = 0;
 1136+
 1137+ if($fp = @fsockopen(
 1138+ $host,
 1139+ $port,
 1140+ $errno,
 1141+ $errstr,
 1142+ $this->_fp_timeout
 1143+ ))
 1144+ {
 1145+ // socket connection succeeded
 1146+
 1147+ return true;
 1148+ }
 1149+ else
 1150+ {
 1151+ // socket connection failed
 1152+ $this->status = $errno;
 1153+ switch($errno)
 1154+ {
 1155+ case -3:
 1156+ $this->error="socket creation failed (-3)";
 1157+ case -4:
 1158+ $this->error="dns lookup failure (-4)";
 1159+ case -5:
 1160+ $this->error="connection refused or timed out (-5)";
 1161+ default:
 1162+ $this->error="connection failed (".$errno.")";
 1163+ }
 1164+ return false;
 1165+ }
 1166+ }
 1167+/*======================================================================*\
 1168+ Function: _disconnect
 1169+ Purpose: disconnect a socket connection
 1170+ Input: $fp file pointer
 1171+\*======================================================================*/
 1172+
 1173+ function _disconnect($fp)
 1174+ {
 1175+ return(@fclose($fp));
 1176+ }
 1177+
 1178+
 1179+/*======================================================================*\
 1180+ Function: _prepare_post_body
 1181+ Purpose: Prepare post body according to encoding type
 1182+ Input: $formvars - form variables
 1183+ $formfiles - form upload files
 1184+ Output: post body
 1185+\*======================================================================*/
 1186+
 1187+ function _prepare_post_body($formvars, $formfiles)
 1188+ {
 1189+ settype($formvars, "array");
 1190+ settype($formfiles, "array");
 1191+ $postdata = '';
 1192+
 1193+ if (count($formvars) == 0 && count($formfiles) == 0)
 1194+ return;
 1195+
 1196+ switch ($this->_submit_type) {
 1197+ case "application/x-www-form-urlencoded":
 1198+ reset($formvars);
 1199+ while(list($key,$val) = each($formvars)) {
 1200+ if (is_array($val) || is_object($val)) {
 1201+ while (list($cur_key, $cur_val) = each($val)) {
 1202+ $postdata .= urlencode($key)."[]=".urlencode($cur_val)."&";
 1203+ }
 1204+ } else
 1205+ $postdata .= urlencode($key)."=".urlencode($val)."&";
 1206+ }
 1207+ break;
 1208+
 1209+ case "multipart/form-data":
 1210+ $this->_mime_boundary = "Snoopy".md5(uniqid(microtime()));
 1211+
 1212+ reset($formvars);
 1213+ while(list($key,$val) = each($formvars)) {
 1214+ if (is_array($val) || is_object($val)) {
 1215+ while (list($cur_key, $cur_val) = each($val)) {
 1216+ $postdata .= "--".$this->_mime_boundary."\r\n";
 1217+ $postdata .= "Content-Disposition: form-data; name=\"$key\[\]\"\r\n\r\n";
 1218+ $postdata .= "$cur_val\r\n";
 1219+ }
 1220+ } else {
 1221+ $postdata .= "--".$this->_mime_boundary."\r\n";
 1222+ $postdata .= "Content-Disposition: form-data; name=\"$key\"\r\n\r\n";
 1223+ $postdata .= "$val\r\n";
 1224+ }
 1225+ }
 1226+
 1227+ reset($formfiles);
 1228+ while (list($field_name, $file_names) = each($formfiles)) {
 1229+ settype($file_names, "array");
 1230+ while (list(, $file_name) = each($file_names)) {
 1231+ if (!is_readable($file_name)) continue;
 1232+
 1233+ $fp = @fopen($file_name, "r");
 1234+ $file_content = @fread($fp, filesize($file_name));
 1235+ @fclose($fp);
 1236+ $base_name = basename($file_name);
 1237+
 1238+ $postdata .= "--".$this->_mime_boundary."\r\n";
 1239+ $postdata .= "Content-Disposition: form-data; name=\"$field_name\"; filename=\"$base_name\"\r\n\r\n";
 1240+ $postdata .= "$file_content\r\n";
 1241+ }
 1242+ }
 1243+ $postdata .= "--".$this->_mime_boundary."--\r\n";
 1244+ break;
 1245+ }
 1246+
 1247+ return $postdata;
 1248+ }
 1249+}
 1250+
 1251+?>
Property changes on: trunk/extensions/WikiSync/Snoopy/Snoopy.class.php
___________________________________________________________________
Added: svn:eol-style
11252 + native
Index: trunk/extensions/WikiSync/Snoopy/AUTHORS
@@ -0,0 +1,11 @@
 2+Monte Ohrt <monte@ispi.net>
 3+ - main Snoopy work
 4+
 5+Andrei Zmievski <andrei@ispi.net>
 6+ - miscellaneous fixes
 7+ - read timeout support
 8+ - file submission capability
 9+
 10+Gene Wood <gene_wood@users.sourceforge.net>
 11+ - bug fixes
 12+ - security fixes
Index: trunk/extensions/WikiSync/Snoopy/TODO
@@ -0,0 +1,9 @@
 2+* fetch other types of protocols such as ftp, nntp, gopher, etc.
 3+* post forms with http file upload (I didn't have this need,
 4+ but it should be fairly straightforward)
 5+* expand links, image tags, and form actions to fully
 6+ qualified URLs
 7+
 8+Bugs
 9+----
 10+* none known
Index: trunk/extensions/WikiSync/Snoopy/INSTALL
@@ -0,0 +1,2 @@
 2+Put Snoopy.class.php into one of the directories specified in your
 3+php.ini include_path directive.
Index: trunk/extensions/WikiSync/Snoopy/ChangeLog
@@ -0,0 +1,105 @@
 2+Version 1.2.4
 3+-------------
 4+
 5+ - fix command line escapement vulnerability with execution of curl binary on https fetches (mohrt)
 6+
 7+Version 1.2.3
 8+-----------
 9+ - updated the version variable in the code to reflect the new version number
 10+ - fixed a typo that I introduced in 1.2.2 (the first character of the file is a "z" (gene_wood, Marc Desrousseaux, Jan Pedersen)
 11+ - fixed BUG # 1328793 : fetch is case sensetive when it comes to the scheme (http / https) (gene_wood)
 12+
 13+Version 1.2.2
 14+-----------
 15+ - incorporated PATCH # 985470 : pass port information in http 1.1 Host header ( http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.23 ) (gene_wood)
 16+ - fixed BUG # 1110049 : redirect is case sensitive
 17+ - fixed bug in security bugfix from 1.2.1 (gene_wood, kellan, zaruba)
 18+
 19+Version 1.2.1
 20+-----------
 21+ - fixed potential security issue with unchecked variables being passed to exec (for https with curl) (gene_wood)
 22+ - fixed BUG # 1086830 : submitlinks,fetchlinks and submittext expandlinks with the URI of the original page not the refreshed page (gene_wood)
 23+ - fixed BUG # 1077870 : Snoopy can't deal with multiple spaces in a refresh tag (gene_wood)
 24+ - fixed BUG # 864047 : Root relative links are treated as relative (gene_wood)
 25+ - fixed BUG # 1097134 : Undefined URI_PARTS["path"] generates Notice (gene_wood)
 26+
 27+Version 1.2
 28+-----------
 29+ - fixed BUG # 1014823 : Meta redirect regex inaccurate (gene_wood)
 30+ - fixed BUG # 999079 : Trailing slashes not removed in uri passed to fetchlinks (gene_wood)
 31+ - fixed BUG # 642958 and 912060 : $URI_PARTS["query"] causing undefined variable notices (gene_wood)
 32+ - fixed BUG # 626849 : cURL security risk (Tajh Leitso, gene_wood)
 33+ - fixed BUG # 626849 : Corrects the redirect function under the submit functions (Tajh Leitso, gene_wood)
 34+ - fixed BUG # 912060 : Undefined variable: postdata (gene_wood)
 35+ - fixed BUG # 858526 : win32 tmp/$headerfile create error (gene_wood)
 36+ - fixed BUG # 929682 : Called undefined function is_executable() on line 194. (gene_wood)
 37+ - fixed BUG # 859711 : typo: http://snoopy.sourceforge.com (gene_wood)
 38+ - fixed BUG # 852993 : double urlencoding breaks redirect (gene_wood)
 39+ - added proxy user/pass support (Robert Zwink, Monte)
 40+ - fixed post data array problem (stefan, Monte)
 41+
 42+Version 1.01
 43+-----------
 44+ - fixed problem with PHP 4.3.2 and fread() (Monte)
 45+
 46+Version 1.0
 47+-----------
 48+ - added textarea to stripform functionality (Monte)
 49+ - fixed multiple cookie setting problem (Monte)
 50+ - fixed problem where extra text inside <frame src (Monte)
 51+ - fixed problem where extra text inside <a href (Monte)
 52+ - removed http request header from curl fetched
 53+ documents, not needed (Monte)
 54+ - added carriage return to newlines on headers (Monte)
 55+ - fixed bug with curl, removed single quotes
 56+ - fixed bug with curl and "&" in the URL
 57+ - added ability to post files. (Andrei)
 58+
 59+Version 0.94
 60+------------
 61+ - Added fetchform() function
 62+ - Fixed misc issues with frames
 63+ - Added SSL support via cURL
 64+ - fixed bug with posting arrays of data
 65+ - added status variable for http status
 66+
 67+Version 0.93
 68+------------
 69+ - fixed bug with hostname match in a redirect location header
 70+ - added $lastredirectaddr variable
 71+
 72+Version 0.92
 73+------------
 74+ - fixed redirect bug with MS web server
 75+ - added ability to pass set cookies through redirects
 76+ - added ability to traverse html frames
 77+
 78+Version 0.91
 79+------------
 80+ - fixed bug with return headers being overwritten.
 81+ Please read the NEWS file for important notes. (Monte)
 82+
 83+Version 0.9
 84+-----------
 85+ - added support for read timeouts (Andrei)
 86+ - standardized distribution (Andrei)
 87+
 88+Version 0.1e
 89+------------
 90+ - fixed bug in fetchlinks logic (Monte)
 91+
 92+Version 0.1d
 93+------------
 94+ - fixed redirect bug without fully qualified url (Monte)
 95+
 96+Version 0.1c
 97+------------
 98+ - fixed bug on submitting formvars after a redirect (Monte)
 99+
 100+Version 0.1b
 101+------------
 102+ - fixed bug to allow empty post vars on a submit (Monte)
 103+
 104+Version 0.1
 105+------------
 106+ - initial release (Monte)
Index: trunk/extensions/WikiSync/Snoopy/FAQ
@@ -0,0 +1,14 @@
 2+Q: Why can't I fetch https pages?
 3+A: Using Snoopy to fetch an https page requires curl. Check if curl is installed on your host. If curl is installed, it may be located in a different place than the default. By default Snoopy looks for curl in /usr/local/bin/curl. Run 'which curl' and find out your location. If it differs from the default, then you'll need to set the $snoopy->curl_path variable to the location of your curl installation. Here's an example of the code :
 4+ include "Snoopy.class.php";
 5+ $snoopy = new Snoopy;
 6+ $snoopy->curl_path="/usr/bin/curl";
 7+
 8+Q: where does the function preg_match_all come from?
 9+A: PCRE functions in PHP 3.0.9 and later
 10+
 11+Q: I get the error: Warning: Wrong parameter count for fsockopen()
 12+A: Upgrade your verion of PHP to 3.0.9 or later
 13+
 14+Q: Snoopy cuts of my results every time. What's wrong?
 15+A: Upgrade your verion of PHP to 3.0.9 or later
Index: trunk/extensions/WikiSync/Snoopy/NEWS
@@ -0,0 +1,61 @@
 2+RELEASE NOTE: v1.2.4
 3+October 22, 2008
 4+
 5+https fetches were not properly escaping shell args for curl binary execution. This is fixed.
 6+
 7+RELEASE NOTE: v1.2.3
 8+November 7, 2005
 9+
 10+A typo was introduced in 1.2.2 which broke the whole release. This has been fixed.
 11+A couple small fixes have been implemented also.
 12+
 13+RELEASE NOTE: v1.2.2
 14+October 30, 2005
 15+
 16+Fixed a bug with the bugfix for the security hole.
 17+
 18+RELEASE NOTE: v1.2.1
 19+October 24, 2005
 20+
 21+Fixed a few outstanding bugs and a potential security hole.
 22+
 23+RELEASE NOTE: v1.2
 24+November 17, 2004
 25+
 26+Fixed a number of outstanding bugs.
 27+
 28+RELEASE NOTE: v1.01
 29+
 30+PHP fixed a bug with fread() which consequently broke the way Snoopy called it. This has been fixed.
 31+Renamed Snoopy.class.inc to Snoopy.class.php for proper file extention.
 32+
 33+RELEASE NOTE: v1.0
 34+
 35+Added fetchform() function for fetching form elements from an html page.
 36+For SSL support, you must have cURL installed. see http://curl.haxx.se
 37+for details. Snoopy does not use the cURL library fuctions within PHP,
 38+as these are not stable as of this Snoopy release.
 39+Fixed bug with posting arrays of data.
 40+Added status variable to track http status.
 41+Several other bug fixes, see Changelog.
 42+RELEASE NOTE: v0.93
 43+
 44+A bug was fixed with redirection headers not containing the hostname, doubling up the redirection location URL.
 45+
 46+There is also a new variable, $lastredirectaddr that contains the last redirection URL.
 47+
 48+RELEASE NOTE: v0.92
 49+March 9, 2000
 50+
 51+A bug was fixed with redirection on MS web servers. Also, cookies are now passed through redirects.
 52+
 53+This release also adds the ability to traverse html framed pages. Just set $maxframes to the recursion depth you want to allow, and results are returned in $this->results as an array. See the README for an example.
 54+
 55+-Monte
 56+
 57+RELEASE NOTE: v0.91
 58+February 22, 2000
 59+
 60+In previous versions of Snoopy, $this->header was an array containing key/value pairs of headers returned from fetched content, not including HTTP and GET headers. If a key value was the same, the old value was overwritten (Two Set-Cookie: headers for example). This was overcome by making $this->header a simple array containing every header returned. Therefore, it will now be up to the programmer to split these headers into key/value pairs if so desired.
 61+
 62+-Monte
Index: trunk/extensions/WikiSync/Snoopy/COPYING.lib
@@ -0,0 +1,458 @@
 2+ GNU LESSER GENERAL PUBLIC LICENSE
 3+ Version 2.1, February 1999
 4+
 5+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
 6+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 7+ Everyone is permitted to copy and distribute verbatim copies
 8+ of this license document, but changing it is not allowed.
 9+
 10+[This is the first released version of the Lesser GPL. It also counts
 11+ as the successor of the GNU Library Public License, version 2, hence
 12+ the version number 2.1.]
 13+
 14+ Preamble
 15+
 16+ The licenses for most software are designed to take away your
 17+freedom to share and change it. By contrast, the GNU General Public
 18+Licenses are intended to guarantee your freedom to share and change
 19+free software--to make sure the software is free for all its users.
 20+
 21+ This license, the Lesser General Public License, applies to some
 22+specially designated software packages--typically libraries--of the
 23+Free Software Foundation and other authors who decide to use it. You
 24+can use it too, but we suggest you first think carefully about whether
 25+this license or the ordinary General Public License is the better
 26+strategy to use in any particular case, based on the explanations below.
 27+
 28+ When we speak of free software, we are referring to freedom of use,
 29+not price. Our General Public Licenses are designed to make sure that
 30+you have the freedom to distribute copies of free software (and charge
 31+for this service if you wish); that you receive source code or can get
 32+it if you want it; that you can change the software and use pieces of
 33+it in new free programs; and that you are informed that you can do
 34+these things.
 35+
 36+ To protect your rights, we need to make restrictions that forbid
 37+distributors to deny you these rights or to ask you to surrender these
 38+rights. These restrictions translate to certain responsibilities for
 39+you if you distribute copies of the library or if you modify it.
 40+
 41+ For example, if you distribute copies of the library, whether gratis
 42+or for a fee, you must give the recipients all the rights that we gave
 43+you. You must make sure that they, too, receive or can get the source
 44+code. If you link other code with the library, you must provide
 45+complete object files to the recipients, so that they can relink them
 46+with the library after making changes to the library and recompiling
 47+it. And you must show them these terms so they know their rights.
 48+
 49+ We protect your rights with a two-step method: (1) we copyright the
 50+library, and (2) we offer you this license, which gives you legal
 51+permission to copy, distribute and/or modify the library.
 52+
 53+ To protect each distributor, we want to make it very clear that
 54+there is no warranty for the free library. Also, if the library is
 55+modified by someone else and passed on, the recipients should know
 56+that what they have is not the original version, so that the original
 57+author's reputation will not be affected by problems that might be
 58+introduced by others.
 59+
 60+ Finally, software patents pose a constant threat to the existence of
 61+any free program. We wish to make sure that a company cannot
 62+effectively restrict the users of a free program by obtaining a
 63+restrictive license from a patent holder. Therefore, we insist that
 64+any patent license obtained for a version of the library must be
 65+consistent with the full freedom of use specified in this license.
 66+
 67+ Most GNU software, including some libraries, is covered by the
 68+ordinary GNU General Public License. This license, the GNU Lesser
 69+General Public License, applies to certain designated libraries, and
 70+is quite different from the ordinary General Public License. We use
 71+this license for certain libraries in order to permit linking those
 72+libraries into non-free programs.
 73+
 74+ When a program is linked with a library, whether statically or using
 75+a shared library, the combination of the two is legally speaking a
 76+combined work, a derivative of the original library. The ordinary
 77+General Public License therefore permits such linking only if the
 78+entire combination fits its criteria of freedom. The Lesser General
 79+Public License permits more lax criteria for linking other code with
 80+the library.
 81+
 82+ We call this license the "Lesser" General Public License because it
 83+does Less to protect the user's freedom than the ordinary General
 84+Public License. It also provides other free software developers Less
 85+of an advantage over competing non-free programs. These disadvantages
 86+are the reason we use the ordinary General Public License for many
 87+libraries. However, the Lesser license provides advantages in certain
 88+special circumstances.
 89+
 90+ For example, on rare occasions, there may be a special need to
 91+encourage the widest possible use of a certain library, so that it becomes
 92+a de-facto standard. To achieve this, non-free programs must be
 93+allowed to use the library. A more frequent case is that a free
 94+library does the same job as widely used non-free libraries. In this
 95+case, there is little to gain by limiting the free library to free
 96+software only, so we use the Lesser General Public License.
 97+
 98+ In other cases, permission to use a particular library in non-free
 99+programs enables a greater number of people to use a large body of
 100+free software. For example, permission to use the GNU C Library in
 101+non-free programs enables many more people to use the whole GNU
 102+operating system, as well as its variant, the GNU/Linux operating
 103+system.
 104+
 105+ Although the Lesser General Public License is Less protective of the
 106+users' freedom, it does ensure that the user of a program that is
 107+linked with the Library has the freedom and the wherewithal to run
 108+that program using a modified version of the Library.
 109+
 110+ The precise terms and conditions for copying, distribution and
 111+modification follow. Pay close attention to the difference between a
 112+"work based on the library" and a "work that uses the library". The
 113+former contains code derived from the library, whereas the latter must
 114+be combined with the library in order to run.
 115+
 116+ GNU LESSER GENERAL PUBLIC LICENSE
 117+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 118+
 119+ 0. This License Agreement applies to any software library or other
 120+program which contains a notice placed by the copyright holder or
 121+other authorized party saying it may be distributed under the terms of
 122+this Lesser General Public License (also called "this License").
 123+Each licensee is addressed as "you".
 124+
 125+ A "library" means a collection of software functions and/or data
 126+prepared so as to be conveniently linked with application programs
 127+(which use some of those functions and data) to form executables.
 128+
 129+ The "Library", below, refers to any such software library or work
 130+which has been distributed under these terms. A "work based on the
 131+Library" means either the Library or any derivative work under
 132+copyright law: that is to say, a work containing the Library or a
 133+portion of it, either verbatim or with modifications and/or translated
 134+straightforwardly into another language. (Hereinafter, translation is
 135+included without limitation in the term "modification".)
 136+
 137+ "Source code" for a work means the preferred form of the work for
 138+making modifications to it. For a library, complete source code means
 139+all the source code for all modules it contains, plus any associated
 140+interface definition files, plus the scripts used to control compilation
 141+and installation of the library.
 142+
 143+ Activities other than copying, distribution and modification are not
 144+covered by this License; they are outside its scope. The act of
 145+running a program using the Library is not restricted, and output from
 146+such a program is covered only if its contents constitute a work based
 147+on the Library (independent of the use of the Library in a tool for
 148+writing it). Whether that is true depends on what the Library does
 149+and what the program that uses the Library does.
 150+
 151+ 1. You may copy and distribute verbatim copies of the Library's
 152+complete source code as you receive it, in any medium, provided that
 153+you conspicuously and appropriately publish on each copy an
 154+appropriate copyright notice and disclaimer of warranty; keep intact
 155+all the notices that refer to this License and to the absence of any
 156+warranty; and distribute a copy of this License along with the
 157+Library.
 158+
 159+ You may charge a fee for the physical act of transferring a copy,
 160+and you may at your option offer warranty protection in exchange for a
 161+fee.
 162+
 163+ 2. You may modify your copy or copies of the Library or any portion
 164+of it, thus forming a work based on the Library, and copy and
 165+distribute such modifications or work under the terms of Section 1
 166+above, provided that you also meet all of these conditions:
 167+
 168+ a) The modified work must itself be a software library.
 169+
 170+ b) You must cause the files modified to carry prominent notices
 171+ stating that you changed the files and the date of any change.
 172+
 173+ c) You must cause the whole of the work to be licensed at no
 174+ charge to all third parties under the terms of this License.
 175+
 176+ d) If a facility in the modified Library refers to a function or a
 177+ table of data to be supplied by an application program that uses
 178+ the facility, other than as an argument passed when the facility
 179+ is invoked, then you must make a good faith effort to ensure that,
 180+ in the event an application does not supply such function or
 181+ table, the facility still operates, and performs whatever part of
 182+ its purpose remains meaningful.
 183+
 184+ (For example, a function in a library to compute square roots has
 185+ a purpose that is entirely well-defined independent of the
 186+ application. Therefore, Subsection 2d requires that any
 187+ application-supplied function or table used by this function must
 188+ be optional: if the application does not supply it, the square
 189+ root function must still compute square roots.)
 190+
 191+These requirements apply to the modified work as a whole. If
 192+identifiable sections of that work are not derived from the Library,
 193+and can be reasonably considered independent and separate works in
 194+themselves, then this License, and its terms, do not apply to those
 195+sections when you distribute them as separate works. But when you
 196+distribute the same sections as part of a whole which is a work based
 197+on the Library, the distribution of the whole must be on the terms of
 198+this License, whose permissions for other licensees extend to the
 199+entire whole, and thus to each and every part regardless of who wrote
 200+it.
 201+
 202+Thus, it is not the intent of this section to claim rights or contest
 203+your rights to work written entirely by you; rather, the intent is to
 204+exercise the right to control the distribution of derivative or
 205+collective works based on the Library.
 206+
 207+In addition, mere aggregation of another work not based on the Library
 208+with the Library (or with a work based on the Library) on a volume of
 209+a storage or distribution medium does not bring the other work under
 210+the scope of this License.
 211+
 212+ 3. You may opt to apply the terms of the ordinary GNU General Public
 213+License instead of this License to a given copy of the Library. To do
 214+this, you must alter all the notices that refer to this License, so
 215+that they refer to the ordinary GNU General Public License, version 2,
 216+instead of to this License. (If a newer version than version 2 of the
 217+ordinary GNU General Public License has appeared, then you can specify
 218+that version instead if you wish.) Do not make any other change in
 219+these notices.
 220+
 221+ Once this change is made in a given copy, it is irreversible for
 222+that copy, so the ordinary GNU General Public License applies to all
 223+subsequent copies and derivative works made from that copy.
 224+
 225+ This option is useful when you wish to copy part of the code of
 226+the Library into a program that is not a library.
 227+
 228+ 4. You may copy and distribute the Library (or a portion or
 229+derivative of it, under Section 2) in object code or executable form
 230+under the terms of Sections 1 and 2 above provided that you accompany
 231+it with the complete corresponding machine-readable source code, which
 232+must be distributed under the terms of Sections 1 and 2 above on a
 233+medium customarily used for software interchange.
 234+
 235+ If distribution of object code is made by offering access to copy
 236+from a designated place, then offering equivalent access to copy the
 237+source code from the same place satisfies the requirement to
 238+distribute the source code, even though third parties are not
 239+compelled to copy the source along with the object code.
 240+
 241+ 5. A program that contains no derivative of any portion of the
 242+Library, but is designed to work with the Library by being compiled or
 243+linked with it, is called a "work that uses the Library". Such a
 244+work, in isolation, is not a derivative work of the Library, and
 245+therefore falls outside the scope of this License.
 246+
 247+ However, linking a "work that uses the Library" with the Library
 248+creates an executable that is a derivative of the Library (because it
 249+contains portions of the Library), rather than a "work that uses the
 250+library". The executable is therefore covered by this License.
 251+Section 6 states terms for distribution of such executables.
 252+
 253+ When a "work that uses the Library" uses material from a header file
 254+that is part of the Library, the object code for the work may be a
 255+derivative work of the Library even though the source code is not.
 256+Whether this is true is especially significant if the work can be
 257+linked without the Library, or if the work is itself a library. The
 258+threshold for this to be true is not precisely defined by law.
 259+
 260+ If such an object file uses only numerical parameters, data
 261+structure layouts and accessors, and small macros and small inline
 262+functions (ten lines or less in length), then the use of the object
 263+file is unrestricted, regardless of whether it is legally a derivative
 264+work. (Executables containing this object code plus portions of the
 265+Library will still fall under Section 6.)
 266+
 267+ Otherwise, if the work is a derivative of the Library, you may
 268+distribute the object code for the work under the terms of Section 6.
 269+Any executables containing that work also fall under Section 6,
 270+whether or not they are linked directly with the Library itself.
 271+
 272+ 6. As an exception to the Sections above, you may also combine or
 273+link a "work that uses the Library" with the Library to produce a
 274+work containing portions of the Library, and distribute that work
 275+under terms of your choice, provided that the terms permit
 276+modification of the work for the customer's own use and reverse
 277+engineering for debugging such modifications.
 278+
 279+ You must give prominent notice with each copy of the work that the
 280+Library is used in it and that the Library and its use are covered by
 281+this License. You must supply a copy of this License. If the work
 282+during execution displays copyright notices, you must include the
 283+copyright notice for the Library among them, as well as a reference
 284+directing the user to the copy of this License. Also, you must do one
 285+of these things:
 286+
 287+ a) Accompany the work with the complete corresponding
 288+ machine-readable source code for the Library including whatever
 289+ changes were used in the work (which must be distributed under
 290+ Sections 1 and 2 above); and, if the work is an executable linked
 291+ with the Library, with the complete machine-readable "work that
 292+ uses the Library", as object code and/or source code, so that the
 293+ user can modify the Library and then relink to produce a modified
 294+ executable containing the modified Library. (It is understood
 295+ that the user who changes the contents of definitions files in the
 296+ Library will not necessarily be able to recompile the application
 297+ to use the modified definitions.)
 298+
 299+ b) Use a suitable shared library mechanism for linking with the
 300+ Library. A suitable mechanism is one that (1) uses at run time a
 301+ copy of the library already present on the user's computer system,
 302+ rather than copying library functions into the executable, and (2)
 303+ will operate properly with a modified version of the library, if
 304+ the user installs one, as long as the modified version is
 305+ interface-compatible with the version that the work was made with.
 306+
 307+ c) Accompany the work with a written offer, valid for at
 308+ least three years, to give the same user the materials
 309+ specified in Subsection 6a, above, for a charge no more
 310+ than the cost of performing this distribution.
 311+
 312+ d) If distribution of the work is made by offering access to copy
 313+ from a designated place, offer equivalent access to copy the above
 314+ specified materials from the same place.
 315+
 316+ e) Verify that the user has already received a copy of these
 317+ materials or that you have already sent this user a copy.
 318+
 319+ For an executable, the required form of the "work that uses the
 320+Library" must include any data and utility programs needed for
 321+reproducing the executable from it. However, as a special exception,
 322+the materials to be distributed need not include anything that is
 323+normally distributed (in either source or binary form) with the major
 324+components (compiler, kernel, and so on) of the operating system on
 325+which the executable runs, unless that component itself accompanies
 326+the executable.
 327+
 328+ It may happen that this requirement contradicts the license
 329+restrictions of other proprietary libraries that do not normally
 330+accompany the operating system. Such a contradiction means you cannot
 331+use both them and the Library together in an executable that you
 332+distribute.
 333+
 334+ 7. You may place library facilities that are a work based on the
 335+Library side-by-side in a single library together with other library
 336+facilities not covered by this License, and distribute such a combined
 337+library, provided that the separate distribution of the work based on
 338+the Library and of the other library facilities is otherwise
 339+permitted, and provided that you do these two things:
 340+
 341+ a) Accompany the combined library with a copy of the same work
 342+ based on the Library, uncombined with any other library
 343+ facilities. This must be distributed under the terms of the
 344+ Sections above.
 345+
 346+ b) Give prominent notice with the combined library of the fact
 347+ that part of it is a work based on the Library, and explaining
 348+ where to find the accompanying uncombined form of the same work.
 349+
 350+ 8. You may not copy, modify, sublicense, link with, or distribute
 351+the Library except as expressly provided under this License. Any
 352+attempt otherwise to copy, modify, sublicense, link with, or
 353+distribute the Library is void, and will automatically terminate your
 354+rights under this License. However, parties who have received copies,
 355+or rights, from you under this License will not have their licenses
 356+terminated so long as such parties remain in full compliance.
 357+
 358+ 9. You are not required to accept this License, since you have not
 359+signed it. However, nothing else grants you permission to modify or
 360+distribute the Library or its derivative works. These actions are
 361+prohibited by law if you do not accept this License. Therefore, by
 362+modifying or distributing the Library (or any work based on the
 363+Library), you indicate your acceptance of this License to do so, and
 364+all its terms and conditions for copying, distributing or modifying
 365+the Library or works based on it.
 366+
 367+ 10. Each time you redistribute the Library (or any work based on the
 368+Library), the recipient automatically receives a license from the
 369+original licensor to copy, distribute, link with or modify the Library
 370+subject to these terms and conditions. You may not impose any further
 371+restrictions on the recipients' exercise of the rights granted herein.
 372+You are not responsible for enforcing compliance by third parties with
 373+this License.
 374+
 375+ 11. If, as a consequence of a court judgment or allegation of patent
 376+infringement or for any other reason (not limited to patent issues),
 377+conditions are imposed on you (whether by court order, agreement or
 378+otherwise) that contradict the conditions of this License, they do not
 379+excuse you from the conditions of this License. If you cannot
 380+distribute so as to satisfy simultaneously your obligations under this
 381+License and any other pertinent obligations, then as a consequence you
 382+may not distribute the Library at all. For example, if a patent
 383+license would not permit royalty-free redistribution of the Library by
 384+all those who receive copies directly or indirectly through you, then
 385+the only way you could satisfy both it and this License would be to
 386+refrain entirely from distribution of the Library.
 387+
 388+If any portion of this section is held invalid or unenforceable under any
 389+particular circumstance, the balance of the section is intended to apply,
 390+and the section as a whole is intended to apply in other circumstances.
 391+
 392+It is not the purpose of this section to induce you to infringe any
 393+patents or other property right claims or to contest validity of any
 394+such claims; this section has the sole purpose of protecting the
 395+integrity of the free software distribution system which is
 396+implemented by public license practices. Many people have made
 397+generous contributions to the wide range of software distributed
 398+through that system in reliance on consistent application of that
 399+system; it is up to the author/donor to decide if he or she is willing
 400+to distribute software through any other system and a licensee cannot
 401+impose that choice.
 402+
 403+This section is intended to make thoroughly clear what is believed to
 404+be a consequence of the rest of this License.
 405+
 406+ 12. If the distribution and/or use of the Library is restricted in
 407+certain countries either by patents or by copyrighted interfaces, the
 408+original copyright holder who places the Library under this License may add
 409+an explicit geographical distribution limitation excluding those countries,
 410+so that distribution is permitted only in or among countries not thus
 411+excluded. In such case, this License incorporates the limitation as if
 412+written in the body of this License.
 413+
 414+ 13. The Free Software Foundation may publish revised and/or new
 415+versions of the Lesser General Public License from time to time.
 416+Such new versions will be similar in spirit to the present version,
 417+but may differ in detail to address new problems or concerns.
 418+
 419+Each version is given a distinguishing version number. If the Library
 420+specifies a version number of this License which applies to it and
 421+"any later version", you have the option of following the terms and
 422+conditions either of that version or of any later version published by
 423+the Free Software Foundation. If the Library does not specify a
 424+license version number, you may choose any version ever published by
 425+the Free Software Foundation.
 426+
 427+ 14. If you wish to incorporate parts of the Library into other free
 428+programs whose distribution conditions are incompatible with these,
 429+write to the author to ask for permission. For software which is
 430+copyrighted by the Free Software Foundation, write to the Free
 431+Software Foundation; we sometimes make exceptions for this. Our
 432+decision will be guided by the two goals of preserving the free status
 433+of all derivatives of our free software and of promoting the sharing
 434+and reuse of software generally.
 435+
 436+ NO WARRANTY
 437+
 438+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
 439+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
 440+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
 441+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
 442+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
 443+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 444+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
 445+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
 446+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
 447+
 448+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
 449+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
 450+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
 451+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
 452+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
 453+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
 454+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
 455+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
 456+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
 457+DAMAGES.
 458+
 459+ END OF TERMS AND CONDITIONS
Index: trunk/extensions/WikiSync/Snoopy/README
@@ -0,0 +1,262 @@
 2+NAME:
 3+
 4+ Snoopy - the PHP net client v1.2.4
 5+
 6+SYNOPSIS:
 7+
 8+ include "Snoopy.class.php";
 9+ $snoopy = new Snoopy;
 10+
 11+ $snoopy->fetchtext("http://www.php.net/");
 12+ print $snoopy->results;
 13+
 14+ $snoopy->fetchlinks("http://www.phpbuilder.com/");
 15+ print $snoopy->results;
 16+
 17+ $submit_url = "http://lnk.ispi.net/texis/scripts/msearch/netsearch.html";
 18+
 19+ $submit_vars["q"] = "amiga";
 20+ $submit_vars["submit"] = "Search!";
 21+ $submit_vars["searchhost"] = "Altavista";
 22+
 23+ $snoopy->submit($submit_url,$submit_vars);
 24+ print $snoopy->results;
 25+
 26+ $snoopy->maxframes=5;
 27+ $snoopy->fetch("http://www.ispi.net/");
 28+ echo "<PRE>\n";
 29+ echo htmlentities($snoopy->results[0]);
 30+ echo htmlentities($snoopy->results[1]);
 31+ echo htmlentities($snoopy->results[2]);
 32+ echo "</PRE>\n";
 33+
 34+ $snoopy->fetchform("http://www.altavista.com");
 35+ print $snoopy->results;
 36+
 37+DESCRIPTION:
 38+
 39+ What is Snoopy?
 40+
 41+ Snoopy is a PHP class that simulates a web browser. It automates the
 42+ task of retrieving web page content and posting forms, for example.
 43+
 44+ Some of Snoopy's features:
 45+
 46+ * easily fetch the contents of a web page
 47+ * easily fetch the text from a web page (strip html tags)
 48+ * easily fetch the the links from a web page
 49+ * supports proxy hosts
 50+ * supports basic user/pass authentication
 51+ * supports setting user_agent, referer, cookies and header content
 52+ * supports browser redirects, and controlled depth of redirects
 53+ * expands fetched links to fully qualified URLs (default)
 54+ * easily submit form data and retrieve the results
 55+ * supports following html frames (added v0.92)
 56+ * supports passing cookies on redirects (added v0.92)
 57+
 58+
 59+REQUIREMENTS:
 60+
 61+ Snoopy requires PHP with PCRE (Perl Compatible Regular Expressions),
 62+ which should be PHP 3.0.9 and up. For read timeout support, it requires
 63+ PHP 4 Beta 4 or later. Snoopy was developed and tested with PHP 3.0.12.
 64+
 65+CLASS METHODS:
 66+
 67+ fetch($URI)
 68+ -----------
 69+
 70+ This is the method used for fetching the contents of a web page.
 71+ $URI is the fully qualified URL of the page to fetch.
 72+ The results of the fetch are stored in $this->results.
 73+ If you are fetching frames, then $this->results
 74+ contains each frame fetched in an array.
 75+
 76+ fetchtext($URI)
 77+ ---------------
 78+
 79+ This behaves exactly like fetch() except that it only returns
 80+ the text from the page, stripping out html tags and other
 81+ irrelevant data.
 82+
 83+ fetchform($URI)
 84+ ---------------
 85+
 86+ This behaves exactly like fetch() except that it only returns
 87+ the form elements from the page, stripping out html tags and other
 88+ irrelevant data.
 89+
 90+ fetchlinks($URI)
 91+ ----------------
 92+
 93+ This behaves exactly like fetch() except that it only returns
 94+ the links from the page. By default, relative links are
 95+ converted to their fully qualified URL form.
 96+
 97+ submit($URI,$formvars)
 98+ ----------------------
 99+
 100+ This submits a form to the specified $URI. $formvars is an
 101+ array of the form variables to pass.
 102+
 103+
 104+ submittext($URI,$formvars)
 105+ --------------------------
 106+
 107+ This behaves exactly like submit() except that it only returns
 108+ the text from the page, stripping out html tags and other
 109+ irrelevant data.
 110+
 111+ submitlinks($URI)
 112+ ----------------
 113+
 114+ This behaves exactly like submit() except that it only returns
 115+ the links from the page. By default, relative links are
 116+ converted to their fully qualified URL form.
 117+
 118+
 119+CLASS VARIABLES: (default value in parenthesis)
 120+
 121+ $host the host to connect to
 122+ $port the port to connect to
 123+ $proxy_host the proxy host to use, if any
 124+ $proxy_port the proxy port to use, if any
 125+ $agent the user agent to masqerade as (Snoopy v0.1)
 126+ $referer referer information to pass, if any
 127+ $cookies cookies to pass if any
 128+ $rawheaders other header info to pass, if any
 129+ $maxredirs maximum redirects to allow. 0=none allowed. (5)
 130+ $offsiteok whether or not to allow redirects off-site. (true)
 131+ $expandlinks whether or not to expand links to fully qualified URLs (true)
 132+ $user authentication username, if any
 133+ $pass authentication password, if any
 134+ $accept http accept types (image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*)
 135+ $error where errors are sent, if any
 136+ $response_code responde code returned from server
 137+ $headers headers returned from server
 138+ $maxlength max return data length
 139+ $read_timeout timeout on read operations (requires PHP 4 Beta 4+)
 140+ set to 0 to disallow timeouts
 141+ $timed_out true if a read operation timed out (requires PHP 4 Beta 4+)
 142+ $maxframes number of frames we will follow
 143+ $status http status of fetch
 144+ $temp_dir temp directory that the webserver can write to. (/tmp)
 145+ $curl_path system path to cURL binary, set to false if none
 146+
 147+
 148+EXAMPLES:
 149+
 150+ Example: fetch a web page and display the return headers and
 151+ the contents of the page (html-escaped):
 152+
 153+ include "Snoopy.class.php";
 154+ $snoopy = new Snoopy;
 155+
 156+ $snoopy->user = "joe";
 157+ $snoopy->pass = "bloe";
 158+
 159+ if($snoopy->fetch("http://www.slashdot.org/"))
 160+ {
 161+ echo "response code: ".$snoopy->response_code."<br>\n";
 162+ while(list($key,$val) = each($snoopy->headers))
 163+ echo $key.": ".$val."<br>\n";
 164+ echo "<p>\n";
 165+
 166+ echo "<PRE>".htmlspecialchars($snoopy->results)."</PRE>\n";
 167+ }
 168+ else
 169+ echo "error fetching document: ".$snoopy->error."\n";
 170+
 171+
 172+
 173+ Example: submit a form and print out the result headers
 174+ and html-escaped page:
 175+
 176+ include "Snoopy.class.php";
 177+ $snoopy = new Snoopy;
 178+
 179+ $submit_url = "http://lnk.ispi.net/texis/scripts/msearch/netsearch.html";
 180+
 181+ $submit_vars["q"] = "amiga";
 182+ $submit_vars["submit"] = "Search!";
 183+ $submit_vars["searchhost"] = "Altavista";
 184+
 185+
 186+ if($snoopy->submit($submit_url,$submit_vars))
 187+ {
 188+ while(list($key,$val) = each($snoopy->headers))
 189+ echo $key.": ".$val."<br>\n";
 190+ echo "<p>\n";
 191+
 192+ echo "<PRE>".htmlspecialchars($snoopy->results)."</PRE>\n";
 193+ }
 194+ else
 195+ echo "error fetching document: ".$snoopy->error."\n";
 196+
 197+
 198+
 199+ Example: showing functionality of all the variables:
 200+
 201+
 202+ include "Snoopy.class.php";
 203+ $snoopy = new Snoopy;
 204+
 205+ $snoopy->proxy_host = "my.proxy.host";
 206+ $snoopy->proxy_port = "8080";
 207+
 208+ $snoopy->agent = "(compatible; MSIE 4.01; MSN 2.5; AOL 4.0; Windows 98)";
 209+ $snoopy->referer = "http://www.microsnot.com/";
 210+
 211+ $snoopy->cookies["SessionID"] = 238472834723489l;
 212+ $snoopy->cookies["favoriteColor"] = "RED";
 213+
 214+ $snoopy->rawheaders["Pragma"] = "no-cache";
 215+
 216+ $snoopy->maxredirs = 2;
 217+ $snoopy->offsiteok = false;
 218+ $snoopy->expandlinks = false;
 219+
 220+ $snoopy->user = "joe";
 221+ $snoopy->pass = "bloe";
 222+
 223+ if($snoopy->fetchtext("http://www.phpbuilder.com"))
 224+ {
 225+ while(list($key,$val) = each($snoopy->headers))
 226+ echo $key.": ".$val."<br>\n";
 227+ echo "<p>\n";
 228+
 229+ echo "<PRE>".htmlspecialchars($snoopy->results)."</PRE>\n";
 230+ }
 231+ else
 232+ echo "error fetching document: ".$snoopy->error."\n";
 233+
 234+
 235+ Example: fetched framed content and display the results
 236+
 237+ include "Snoopy.class.php";
 238+ $snoopy = new Snoopy;
 239+
 240+ $snoopy->maxframes = 5;
 241+
 242+ if($snoopy->fetch("http://www.ispi.net/"))
 243+ {
 244+ echo "<PRE>".htmlspecialchars($snoopy->results[0])."</PRE>\n";
 245+ echo "<PRE>".htmlspecialchars($snoopy->results[1])."</PRE>\n";
 246+ echo "<PRE>".htmlspecialchars($snoopy->results[2])."</PRE>\n";
 247+ }
 248+ else
 249+ echo "error fetching document: ".$snoopy->error."\n";
 250+
 251+
 252+COPYRIGHT:
 253+ Copyright(c) 1999,2000 ispi. All rights reserved.
 254+ This software is released under the GNU General Public License.
 255+ Please read the disclaimer at the top of the Snoopy.class.php file.
 256+
 257+
 258+THANKS:
 259+ Special Thanks to:
 260+ Peter Sorger <sorgo@cool.sk> help fixing a redirect bug
 261+ Andrei Zmievski <andrei@ispi.net> implementing time out functionality
 262+ Patric Sandelin <patric@kajen.com> help with fetchform debugging
 263+ Carmelo <carmelo@meltingsoft.com> misc bug fixes with frames
Index: trunk/extensions/WikiSync/WikiSync.css
@@ -0,0 +1,23 @@
 2+table.wikisync_remote_login input[type="text"], table.wikisync_remote_login input[type="password"] {
 3+ width: 15em;
 4+}
 5+
 6+div#wikisync_remote_log {
 7+ color: darkblue;
 8+ background-color: lightgray;
 9+ border: 1px solid gray;
 10+ width: 30em;
 11+ height: 12em;
 12+ overflow: auto;
 13+ padding: 5px;
 14+}
 15+
 16+div#wikisync_remote_log hr {
 17+ margin: 0.5em 0 0.5em 0;
 18+}
 19+
 20+table.wikisync_percents_indicator {
 21+ width: 100%;
 22+ height: 15px;
 23+ border-collapse: collapse;
 24+}
Property changes on: trunk/extensions/WikiSync/WikiSync.css
___________________________________________________________________
Added: svn:eol-style
125 + native
Index: trunk/extensions/WikiSync/WikiSyncBasic.php
@@ -0,0 +1,216 @@
 2+<?php
 3+/**
 4+ * ***** BEGIN LICENSE BLOCK *****
 5+ * This file is part of WikiSync.
 6+ *
 7+ * WikiSync 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+ * WikiSync 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
 18+ * along with WikiSync; if not, write to the Free Software
 19+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 20+ *
 21+ * ***** END LICENSE BLOCK *****
 22+ *
 23+ * WikiSync allows an AJAX-based synchronization of revisions and files between
 24+ * global wiki site and it's local mirror.
 25+ *
 26+ * To activate this extension :
 27+ * * Create a new directory named WikiSync into the directory "extensions" of MediaWiki.
 28+ * * Place the files from the extension archive there.
 29+ * * Add this line at the end of your LocalSettings.php file :
 30+ * require_once "$IP/extensions/WikiSync/WikiSync.php";
 31+ *
 32+ * @version 0.2.0
 33+ * @link http://www.mediawiki.org/wiki/Extension:WikiSync
 34+ * @author Dmitriy Sintsov <questpc@rambler.ru>
 35+ * @addtogroup Extensions
 36+ */
 37+
 38+if ( !defined( 'MEDIAWIKI' ) ) {
 39+ die( "This file is a part of MediaWiki extension.\n" );
 40+}
 41+
 42+/* render output data */
 43+class _QXML {
 44+ // the stucture of $tag is like this:
 45+ // array( "__tag"=>"td", "class"=>"myclass", 0=>"text before li", 1=>array( "__tag"=>"li", 0=>"text inside li" ), 2=>"text after li" )
 46+ // both tagged and tagless lists are supported
 47+ static function toText( &$tag ) {
 48+ $tag_open = "";
 49+ $tag_close = "";
 50+ $tag_val = null;
 51+ if ( is_array( $tag ) ) {
 52+ ksort( $tag );
 53+ if ( array_key_exists( '__tag', $tag ) ) {
 54+ # list inside of tag
 55+ $tag_open .= "<" . $tag[ '__tag' ];
 56+ foreach ( $tag as $attr_key => &$attr_val ) {
 57+ if ( is_int( $attr_key ) ) {
 58+ if ( $tag_val === null )
 59+ $tag_val = "";
 60+ if ( is_array( $attr_val ) ) {
 61+ # recursive tags
 62+ $tag_val .= self::toText( $attr_val );
 63+ } else {
 64+ # text
 65+ $tag_val .= $attr_val;
 66+ }
 67+ } else {
 68+ # string keys are for tag attributes
 69+ if ( substr( $attr_key, 0, 2 ) != "__" ) {
 70+ # include only non-reserved attributes
 71+ $tag_open .= " $attr_key=\"" . $attr_val . "\"";
 72+ }
 73+ }
 74+ }
 75+ if ( $tag_val !== null ) {
 76+ $tag_open .= ">";
 77+ $tag_close .= "</" . $tag[ '__tag' ] . ">";
 78+ } else {
 79+ $tag_open .= " />";
 80+ }
 81+ if ( array_key_exists( '__end', $tag ) ) {
 82+ $tag_close .= $tag[ '__end' ];
 83+ }
 84+ } else {
 85+ # tagless list
 86+ $tag_val = "";
 87+ foreach ( $tag as $attr_key => &$attr_val ) {
 88+ if ( is_int( $attr_key ) ) {
 89+ if ( is_array( $attr_val ) ) {
 90+ # recursive tags
 91+ $tag_val .= self::toText( $attr_val );
 92+ } else {
 93+ # text
 94+ $tag_val .= $attr_val;
 95+ }
 96+ } else {
 97+ ob_start();
 98+ var_dump( $tag );
 99+ $tagdump = ob_get_contents();
 100+ ob_end_clean();
 101+ $tag_val = "invalid argument: tagless list cannot have tag attribute values in key=$attr_key, $tagdump";
 102+ }
 103+ }
 104+ }
 105+ } else {
 106+ # just a text
 107+ $tag_val = $tag;
 108+ }
 109+ return $tag_open . $tag_val . $tag_close;
 110+ }
 111+
 112+ # creates one "htmlobject" row of the table
 113+ # elements of $row can be either a string/number value of cell or an array( "count"=>colspannum, "attribute"=>value, 0=>html_inside_tag )
 114+ # attribute maps can be like this: ("name"=>0, "count"=>colspan" )
 115+ static function newRow( $row, $rowattrs = "", $celltag = "td", $attribute_maps = null ) {
 116+ $result = "";
 117+ if ( count( $row ) > 0 ) {
 118+ foreach ( $row as &$cell ) {
 119+ if ( !is_array( $cell ) ) {
 120+ $cell = array( 0 => $cell );
 121+ }
 122+ $cell[ '__tag' ] = $celltag;
 123+ $cell[ '__end' ] = "\n";
 124+ if ( is_array( $attribute_maps ) ) {
 125+ # converts ("count"=>3) to ("colspan"=>3) in table headers - don't use frequently
 126+ foreach ( $attribute_maps as $key => $val ) {
 127+ if ( array_key_exists( $key, $cell ) ) {
 128+ $cell[ $val ] = $cell[ $key ];
 129+ unset( $cell[ $key ] );
 130+ }
 131+ }
 132+ }
 133+ }
 134+ $result = array( '__tag' => 'tr', 0 => $row, '__end' => "\n" );
 135+ if ( is_array( $rowattrs ) ) {
 136+ $result = array_merge( $rowattrs, $result );
 137+ } elseif ( $rowattrs !== "" ) {
 138+ $result[0][] = __METHOD__ . ':invalid rowattrs supplied';
 139+ }
 140+ }
 141+ return $result;
 142+ }
 143+
 144+ # add row to the table
 145+ static function addRow( &$table, $row, $rowattrs = "", $celltag = "td", $attribute_maps = null ) {
 146+ $table[] = self::newRow( $row, $rowattrs, $celltag, $attribute_maps );
 147+ }
 148+
 149+ # add column to the table
 150+ static function addColumn( &$table, $column, $rowattrs = "", $celltag = "td", $attribute_maps = null ) {
 151+ if ( count( $column ) > 0 ) {
 152+ $row = 0;
 153+ foreach ( $column as &$cell ) {
 154+ if ( !is_array( $cell ) ) {
 155+ $cell = array( 0 => $cell );
 156+ }
 157+ $cell[ '__tag' ] = $celltag;
 158+ $cell[ '__end' ] = "\n";
 159+ if ( is_array( $attribute_maps ) ) {
 160+ # converts ("count"=>3) to ("rowspan"=>3) in table headers - don't use frequently
 161+ foreach ( $attribute_maps as $key => $val ) {
 162+ if ( array_key_exists( $key, $cell ) ) {
 163+ $cell[ $val ] = $cell[ $key ];
 164+ unset( $cell[ $key ] );
 165+ }
 166+ }
 167+ }
 168+ if ( is_array( $rowattrs ) ) {
 169+ $cell = array_merge( $rowattrs, $cell );
 170+ } elseif ( $rowattrs !== "" ) {
 171+ $cell[ 0 ] = __METHOD__ . ':invalid rowattrs supplied';
 172+ }
 173+ if ( !array_key_exists( $row, $table ) ) {
 174+ $table[ $row ] = array( '__tag' => 'tr', '__end' => "\n" );
 175+ }
 176+ $table[ $row ][] = $cell;
 177+ if ( array_key_exists( 'rowspan', $cell ) ) {
 178+ $row += intval( $cell[ 'rowspan' ] );
 179+ } else {
 180+ $row++;
 181+ }
 182+ }
 183+ $result = array( '__tag' => 'tr', 0 => $column, '__end' => "\n" );
 184+ }
 185+ }
 186+
 187+ static function displayRow( $row, $rowattrs = "", $celltag = "td", $attribute_maps = null ) {
 188+ return self::toText( self::newRow( $row, $rowattrs, $celltag, $attribute_maps ) );
 189+ }
 190+
 191+ // use newRow() or addColumn() to add resulting row/column to the table
 192+ // if you want to use the resulting row with toText(), don't forget to apply attrs=array('__tag'=>'td')
 193+ static function applyAttrsToRow( &$row, $attrs ) {
 194+ if ( is_array( $attrs ) && count( $attrs > 0 ) ) {
 195+ foreach ( $row as &$cell ) {
 196+ if ( !is_array( $cell ) ) {
 197+ $cell = array_merge( $attrs, array( $cell ) );
 198+ } else {
 199+ foreach ( $attrs as $attr_key => $attr_val ) {
 200+ if ( !array_key_exists( $attr_key, $cell ) ) {
 201+ $cell[ $attr_key ] = $attr_val;
 202+ }
 203+ }
 204+ }
 205+ }
 206+ }
 207+ }
 208+
 209+ static function entities( $s ) {
 210+ return htmlentities( $s, ENT_COMPAT, 'UTF-8' );
 211+ }
 212+
 213+ static function specialchars( $s ) {
 214+ return htmlspecialchars( $s, ENT_COMPAT, 'UTF-8' );
 215+ }
 216+
 217+} /* end of _QXML class */
Property changes on: trunk/extensions/WikiSync/WikiSyncBasic.php
___________________________________________________________________
Added: svn:eol-style
1218 + native
Index: trunk/extensions/WikiSync/WikiSync_utils.js
@@ -0,0 +1,70 @@
 2+/**
 3+ * @param id - id of table container for percents indicator
 4+ */
 5+function WikiSyncPercentsIndicator( id ) {
 6+ this.topElement = document.getElementById( id );
 7+ var tr1 = this.topElement.firstChild.firstChild;
 8+ // description line will be stored there
 9+ this.descriptionContainer = tr1.firstChild;
 10+ var tr2 = tr1.nextSibling;
 11+ // td1 and td2 are used together as percent indicators
 12+ this.td1 = tr2.firstChild;
 13+ this.td2 = this.td1.nextSibling;
 14+ this.reset();
 15+}
 16+WikiSyncPercentsIndicator.prototype.setVisibility = function( visible ) {
 17+ this.topElement.style.display = visible ? 'block' : 'none';
 18+}
 19+/**
 20+ * @access private
 21+ */
 22+WikiSyncPercentsIndicator.prototype.setPercents = function( element, percent ) {
 23+ element.style.display = (percent > 0) ? 'table-cell' : 'none';
 24+ element.style.width = percent + '%';
 25+}
 26+WikiSyncPercentsIndicator.prototype.reset = function() {
 27+ this.iterations = { 'desc' : '', 'curr' : 0, 'min' : 0, 'max' : 0 };
 28+ this.display();
 29+},
 30+WikiSyncPercentsIndicator.prototype.display = function( indicator ) {
 31+ if ( typeof indicator !== 'undefined' ) {
 32+ if ( typeof indicator.desc !== 'undefined' ) {
 33+ this.iterations.desc = '' + indicator.desc;
 34+ }
 35+ if ( typeof indicator.curr !== 'undefined' ) {
 36+ if ( indicator.curr === 'max' ) {
 37+ this.iterations.curr = this.iterations.max;
 38+ } else if ( indicator.curr === 'next' ) {
 39+ this.iterations.curr++;
 40+ } else {
 41+ this.iterations.curr = parseInt( indicator.curr );
 42+ }
 43+ }
 44+ if ( typeof indicator.min !== 'undefined' ) {
 45+ this.iterations.min = parseInt( indicator.min );
 46+ }
 47+ if ( typeof indicator.max !== 'undefined' ) {
 48+ this.iterations.max = parseInt( indicator.max );
 49+ }
 50+ }
 51+ // display process description
 52+ var text = document.createTextNode( this.iterations.desc );
 53+ if ( this.descriptionContainer.firstChild === null ) {
 54+ this.descriptionContainer.appendChild( text );
 55+ } else {
 56+ this.descriptionContainer.replaceChild( text, this.descriptionContainer.firstChild );
 57+ }
 58+ // calculate percent
 59+ var percent;
 60+ var len = this.iterations.max - this.iterations.min;
 61+ if ( len === 0 ) {
 62+ percent = 0;
 63+ } else {
 64+ percent = ( this.iterations.curr - this.iterations.min ) / len * 100;
 65+ }
 66+ if ( percent < 0 ) { percent = 0 }
 67+ if ( percent > 100 ) { percent = 100 }
 68+ // show percent
 69+ this.setPercents( this.td1, percent );
 70+ this.setPercents( this.td2, 100 - percent );
 71+} /* end of WikiSyncPercentsIndicator class */
Property changes on: trunk/extensions/WikiSync/WikiSync_utils.js
___________________________________________________________________
Added: svn:eol-style
172 + native
Index: trunk/extensions/WikiSync/README
@@ -0,0 +1,10 @@
 2+MediaWiki extension WikiSync, version 0.2.0
 3+
 4+WikiSync allows an AJAX-based synchronization of revisions and files between
 5+global wiki site and it's local mirror. Files download can optionally be disabled,
 6+to speed up the synchronization. The process is partially automatized, and
 7+progress indicator is displayed during time-consuming operations. Please
 8+use not too old (IE8+, FF3+) browser for client interface and client scripts, as
 9+it's not been tested with old browsers.
 10+
 11+See http://www.mediawiki.org/wiki/Extension:WikiSync for further details.
\ No newline at end of file
Index: trunk/extensions/WikiSync/INSTALL
@@ -0,0 +1,8 @@
 2+MediaWiki extension WikiSync, version 0.2.0
 3+
 4+* download the latest available version and extract it to your wiki extension directory.
 5+* add the following line to LocalSettings.php
 6+require_once( "$IP/extensions/WikiSync/WikiSync.php" );
 7+* check out Special:Version page to verify the installation
 8+
 9+See http://www.mediawiki.org/wiki/Extension:WikiSync for further details.
\ No newline at end of file
Index: trunk/extensions/WikiSync/COPYING
@@ -0,0 +1,309 @@
 2+The WikiSync extension may be copied and redistributed under either the
 3+DWTFYWWI license or the GNU General Public License, at the option of the
 4+licensee. The text of both licenses is given below.
 5+
 6+The majority of this extension is written by (and copyright) Tim Starling. Minor
 7+modifications have been made by various members of the MediaWiki development
 8+team.
 9+
 10+-------------------------------------------------------------------------------
 11+
 12+ DWTFYWWI LICENSE
 13+ Version 1, January 2006
 14+
 15+ Copyright (C) 2010 Dmitriy Sintsov (QuestPC)
 16+
 17+ Preamble
 18+
 19+ The licenses for most software are designed to take away your
 20+freedom to share and change it. By contrast, the DWTFYWWI or Do
 21+Whatever The Fuck You Want With It license is intended to guarantee
 22+your freedom to share and change the software--to make sure the
 23+software is free for all its users.
 24+
 25+ DWTFYWWI LICENSE
 26+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 27+0. The author grants everyone permission to do whatever the fuck they
 28+want with the software, whatever the fuck that may be.
 29+
 30+-------------------------------------------------------------------------------
 31+
 32+ GNU GENERAL PUBLIC LICENSE
 33+ Version 2, June 1991
 34+
 35+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
 36+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 37+ Everyone is permitted to copy and distribute verbatim copies
 38+ of this license document, but changing it is not allowed.
 39+
 40+ Preamble
 41+
 42+ The licenses for most software are designed to take away your
 43+freedom to share and change it. By contrast, the GNU General Public
 44+License is intended to guarantee your freedom to share and change free
 45+software--to make sure the software is free for all its users. This
 46+General Public License applies to most of the Free Software
 47+Foundation's software and to any other program whose authors commit to
 48+using it. (Some other Free Software Foundation software is covered by
 49+the GNU Lesser General Public License instead.) You can apply it to
 50+your programs, too.
 51+
 52+ When we speak of free software, we are referring to freedom, not
 53+price. Our General Public Licenses are designed to make sure that you
 54+have the freedom to distribute copies of free software (and charge for
 55+this service if you wish), that you receive source code or can get it
 56+if you want it, that you can change the software or use pieces of it
 57+in new free programs; and that you know you can do these things.
 58+
 59+ To protect your rights, we need to make restrictions that forbid
 60+anyone to deny you these rights or to ask you to surrender the rights.
 61+These restrictions translate to certain responsibilities for you if you
 62+distribute copies of the software, or if you modify it.
 63+
 64+ For example, if you distribute copies of such a program, whether
 65+gratis or for a fee, you must give the recipients all the rights that
 66+you have. You must make sure that they, too, receive or can get the
 67+source code. And you must show them these terms so they know their
 68+rights.
 69+
 70+ We protect your rights with two steps: (1) copyright the software, and
 71+(2) offer you this license which gives you legal permission to copy,
 72+distribute and/or modify the software.
 73+
 74+ Also, for each author's protection and ours, we want to make certain
 75+that everyone understands that there is no warranty for this free
 76+software. If the software is modified by someone else and passed on, we
 77+want its recipients to know that what they have is not the original, so
 78+that any problems introduced by others will not reflect on the original
 79+authors' reputations.
 80+
 81+ Finally, any free program is threatened constantly by software
 82+patents. We wish to avoid the danger that redistributors of a free
 83+program will individually obtain patent licenses, in effect making the
 84+program proprietary. To prevent this, we have made it clear that any
 85+patent must be licensed for everyone's free use or not licensed at all.
 86+
 87+ The precise terms and conditions for copying, distribution and
 88+modification follow.
 89+
 90+ GNU GENERAL PUBLIC LICENSE
 91+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 92+
 93+ 0. This License applies to any program or other work which contains
 94+a notice placed by the copyright holder saying it may be distributed
 95+under the terms of this General Public License. The "Program", below,
 96+refers to any such program or work, and a "work based on the Program"
 97+means either the Program or any derivative work under copyright law:
 98+that is to say, a work containing the Program or a portion of it,
 99+either verbatim or with modifications and/or translated into another
 100+language. (Hereinafter, translation is included without limitation in
 101+the term "modification".) Each licensee is addressed as "you".
 102+
 103+Activities other than copying, distribution and modification are not
 104+covered by this License; they are outside its scope. The act of
 105+running the Program is not restricted, and the output from the Program
 106+is covered only if its contents constitute a work based on the
 107+Program (independent of having been made by running the Program).
 108+Whether that is true depends on what the Program does.
 109+
 110+ 1. You may copy and distribute verbatim copies of the Program's
 111+source code as you receive it, in any medium, provided that you
 112+conspicuously and appropriately publish on each copy an appropriate
 113+copyright notice and disclaimer of warranty; keep intact all the
 114+notices that refer to this License and to the absence of any warranty;
 115+and give any other recipients of the Program a copy of this License
 116+along with the Program.
 117+
 118+You may charge a fee for the physical act of transferring a copy, and
 119+you may at your option offer warranty protection in exchange for a fee.
 120+
 121+ 2. You may modify your copy or copies of the Program or any portion
 122+of it, thus forming a work based on the Program, and copy and
 123+distribute such modifications or work under the terms of Section 1
 124+above, provided that you also meet all of these conditions:
 125+
 126+ a) You must cause the modified files to carry prominent notices
 127+ stating that you changed the files and the date of any change.
 128+
 129+ b) You must cause any work that you distribute or publish, that in
 130+ whole or in part contains or is derived from the Program or any
 131+ part thereof, to be licensed as a whole at no charge to all third
 132+ parties under the terms of this License.
 133+
 134+ c) If the modified program normally reads commands interactively
 135+ when run, you must cause it, when started running for such
 136+ interactive use in the most ordinary way, to print or display an
 137+ announcement including an appropriate copyright notice and a
 138+ notice that there is no warranty (or else, saying that you provide
 139+ a warranty) and that users may redistribute the program under
 140+ these conditions, and telling the user how to view a copy of this
 141+ License. (Exception: if the Program itself is interactive but
 142+ does not normally print such an announcement, your work based on
 143+ the Program is not required to print an announcement.)
 144+
 145+These requirements apply to the modified work as a whole. If
 146+identifiable sections of that work are not derived from the Program,
 147+and can be reasonably considered independent and separate works in
 148+themselves, then this License, and its terms, do not apply to those
 149+sections when you distribute them as separate works. But when you
 150+distribute the same sections as part of a whole which is a work based
 151+on the Program, the distribution of the whole must be on the terms of
 152+this License, whose permissions for other licensees extend to the
 153+entire whole, and thus to each and every part regardless of who wrote it.
 154+
 155+Thus, it is not the intent of this section to claim rights or contest
 156+your rights to work written entirely by you; rather, the intent is to
 157+exercise the right to control the distribution of derivative or
 158+collective works based on the Program.
 159+
 160+In addition, mere aggregation of another work not based on the Program
 161+with the Program (or with a work based on the Program) on a volume of
 162+a storage or distribution medium does not bring the other work under
 163+the scope of this License.
 164+
 165+ 3. You may copy and distribute the Program (or a work based on it,
 166+under Section 2) in object code or executable form under the terms of
 167+Sections 1 and 2 above provided that you also do one of the following:
 168+
 169+ a) Accompany it with the complete corresponding machine-readable
 170+ source code, which must be distributed under the terms of Sections
 171+ 1 and 2 above on a medium customarily used for software interchange; or,
 172+
 173+ b) Accompany it with a written offer, valid for at least three
 174+ years, to give any third party, for a charge no more than your
 175+ cost of physically performing source distribution, a complete
 176+ machine-readable copy of the corresponding source code, to be
 177+ distributed under the terms of Sections 1 and 2 above on a medium
 178+ customarily used for software interchange; or,
 179+
 180+ c) Accompany it with the information you received as to the offer
 181+ to distribute corresponding source code. (This alternative is
 182+ allowed only for noncommercial distribution and only if you
 183+ received the program in object code or executable form with such
 184+ an offer, in accord with Subsection b above.)
 185+
 186+The source code for a work means the preferred form of the work for
 187+making modifications to it. For an executable work, complete source
 188+code means all the source code for all modules it contains, plus any
 189+associated interface definition files, plus the scripts used to
 190+control compilation and installation of the executable. However, as a
 191+special exception, the source code distributed need not include
 192+anything that is normally distributed (in either source or binary
 193+form) with the major components (compiler, kernel, and so on) of the
 194+operating system on which the executable runs, unless that component
 195+itself accompanies the executable.
 196+
 197+If distribution of executable or object code is made by offering
 198+access to copy from a designated place, then offering equivalent
 199+access to copy the source code from the same place counts as
 200+distribution of the source code, even though third parties are not
 201+compelled to copy the source along with the object code.
 202+
 203+ 4. You may not copy, modify, sublicense, or distribute the Program
 204+except as expressly provided under this License. Any attempt
 205+otherwise to copy, modify, sublicense or distribute the Program is
 206+void, and will automatically terminate your rights under this License.
 207+However, parties who have received copies, or rights, from you under
 208+this License will not have their licenses terminated so long as such
 209+parties remain in full compliance.
 210+
 211+ 5. You are not required to accept this License, since you have not
 212+signed it. However, nothing else grants you permission to modify or
 213+distribute the Program or its derivative works. These actions are
 214+prohibited by law if you do not accept this License. Therefore, by
 215+modifying or distributing the Program (or any work based on the
 216+Program), you indicate your acceptance of this License to do so, and
 217+all its terms and conditions for copying, distributing or modifying
 218+the Program or works based on it.
 219+
 220+ 6. Each time you redistribute the Program (or any work based on the
 221+Program), the recipient automatically receives a license from the
 222+original licensor to copy, distribute or modify the Program subject to
 223+these terms and conditions. You may not impose any further
 224+restrictions on the recipients' exercise of the rights granted herein.
 225+You are not responsible for enforcing compliance by third parties to
 226+this License.
 227+
 228+ 7. If, as a consequence of a court judgment or allegation of patent
 229+infringement or for any other reason (not limited to patent issues),
 230+conditions are imposed on you (whether by court order, agreement or
 231+otherwise) that contradict the conditions of this License, they do not
 232+excuse you from the conditions of this License. If you cannot
 233+distribute so as to satisfy simultaneously your obligations under this
 234+License and any other pertinent obligations, then as a consequence you
 235+may not distribute the Program at all. For example, if a patent
 236+license would not permit royalty-free redistribution of the Program by
 237+all those who receive copies directly or indirectly through you, then
 238+the only way you could satisfy both it and this License would be to
 239+refrain entirely from distribution of the Program.
 240+
 241+If any portion of this section is held invalid or unenforceable under
 242+any particular circumstance, the balance of the section is intended to
 243+apply and the section as a whole is intended to apply in other
 244+circumstances.
 245+
 246+It is not the purpose of this section to induce you to infringe any
 247+patents or other property right claims or to contest validity of any
 248+such claims; this section has the sole purpose of protecting the
 249+integrity of the free software distribution system, which is
 250+implemented by public license practices. Many people have made
 251+generous contributions to the wide range of software distributed
 252+through that system in reliance on consistent application of that
 253+system; it is up to the author/donor to decide if he or she is willing
 254+to distribute software through any other system and a licensee cannot
 255+impose that choice.
 256+
 257+This section is intended to make thoroughly clear what is believed to
 258+be a consequence of the rest of this License.
 259+
 260+ 8. If the distribution and/or use of the Program is restricted in
 261+certain countries either by patents or by copyrighted interfaces, the
 262+original copyright holder who places the Program under this License
 263+may add an explicit geographical distribution limitation excluding
 264+those countries, so that distribution is permitted only in or among
 265+countries not thus excluded. In such case, this License incorporates
 266+the limitation as if written in the body of this License.
 267+
 268+ 9. The Free Software Foundation may publish revised and/or new versions
 269+of the General Public License from time to time. Such new versions will
 270+be similar in spirit to the present version, but may differ in detail to
 271+address new problems or concerns.
 272+
 273+Each version is given a distinguishing version number. If the Program
 274+specifies a version number of this License which applies to it and "any
 275+later version", you have the option of following the terms and conditions
 276+either of that version or of any later version published by the Free
 277+Software Foundation. If the Program does not specify a version number of
 278+this License, you may choose any version ever published by the Free Software
 279+Foundation.
 280+
 281+ 10. If you wish to incorporate parts of the Program into other free
 282+programs whose distribution conditions are different, write to the author
 283+to ask for permission. For software which is copyrighted by the Free
 284+Software Foundation, write to the Free Software Foundation; we sometimes
 285+make exceptions for this. Our decision will be guided by the two goals
 286+of preserving the free status of all derivatives of our free software and
 287+of promoting the sharing and reuse of software generally.
 288+
 289+ NO WARRANTY
 290+
 291+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
 292+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
 293+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
 294+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
 295+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 296+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
 297+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
 298+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
 299+REPAIR OR CORRECTION.
 300+
 301+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
 302+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
 303+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
 304+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
 305+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
 306+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
 307+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
 308+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
 309+POSSIBILITY OF SUCH DAMAGES.
 310+
Index: trunk/extensions/WikiSync/WikiSync_rtl.css
Property changes on: trunk/extensions/WikiSync/WikiSync_rtl.css
___________________________________________________________________
Added: svn:eol-style
1311 + native
Index: trunk/extensions/WikiSync/WikiSync.js
@@ -0,0 +1,904 @@
 2+/**
 3+ * ***** BEGIN LICENSE BLOCK *****
 4+ * This file is part of WikiSync.
 5+ *
 6+ * WikiSync is free software; you can redistribute it and/or modify
 7+ * it under the terms of the GNU General Public License as published by
 8+ * the Free Software Foundation; either version 2 of the License, or
 9+ * (at your option) any later version.
 10+ *
 11+ * WikiSync is distributed in the hope that it will be useful,
 12+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 13+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 14+ * GNU General Public License for more details.
 15+ *
 16+ * You should have received a copy of the GNU General Public License
 17+ * along with WikiSync; if not, write to the Free Software
 18+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 19+ *
 20+ * ***** END LICENSE BLOCK *****
 21+ *
 22+ * WikiSync allows an AJAX-based synchronization of revisions and files between
 23+ * global wiki site and it's local mirror.
 24+ *
 25+ * To activate this extension :
 26+ * * Create a new directory named WikiSync into the directory "extensions" of MediaWiki.
 27+ * * Place the files from the extension archive there.
 28+ * * Add this line at the end of your LocalSettings.php file :
 29+ * require_once "$IP/extensions/WikiSync/WikiSync.php";
 30+ *
 31+ * @version 0.2.0
 32+ * @link http://www.mediawiki.org/wiki/Extension:WikiSync
 33+ * @author Dmitriy Sintsov <questpc@rambler.ru>
 34+ * @addtogroup Extensions
 35+ */
 36+
 37+var WikiSync = {
 38+
 39+ _WikiSync : '', // WikiSync context
 40+
 41+ // by default, synchronize from remote to local
 42+ directionToLocal : true,
 43+
 44+ // address of source and destination wikis
 45+ srcWikiRoot : '',
 46+ dstWikiRoot : '',
 47+
 48+ // revision ids of source wiki (dichotomy search)
 49+ // note that all of these should be numbers, while API/callback parameters should be string
 50+ srcFirstId : null, // very first revid
 51+ srcLoId : null, // current first revid
 52+ srcMidId : null, // current "middle" revid
 53+ srcHiId : null, // current last revid
 54+ srcLastId : null, // very last revid
 55+
 56+ srcSyncId : null, // source revision to start exporting from into destination wiki
 57+
 58+ // current "middle" revision of source wiki
 59+ srcRev : null,
 60+
 61+ // import token for destination wiki
 62+ dstImportToken : '',
 63+
 64+ // continuation revision id for exporting from source wiki into destination wiki
 65+ xmlContinueStartId : null,
 66+
 67+ syncPercents : null, // xml chunks transfer progress indicator
 68+ filesPercents : null, // file transfer progress indicator
 69+
 70+ // list of files to synchronize and their index
 71+ syncFiles : false, // true, when files should be synched as well
 72+ fileList : [], // list of files in format of {'title':,'size':,'timestamp':}
 73+ fileListIdx : 0, // current index of the list of files
 74+ // currently _accumulated_ size of all files in fileList (counted in chunks, file by fule)
 75+ fileListSize : 0,
 76+ // currently accumulated offset (which is also a current size) of currently transferred file
 77+ currFileOffset : 0,
 78+
 79+ // {{{ remote login context
 80+ remoteContext : {
 81+ 'wikiroot' : '',
 82+ 'userid' : '',
 83+ 'username' : '',
 84+ 'logintoken' : '',
 85+ 'cookieprefix' : '',
 86+ 'sessionid' : ''
 87+ },
 88+ // }}}
 89+
 90+ // result of AJAX call will be placed here (in JSON format)
 91+ AJAXresult : {},
 92+
 93+ // progress indicator min, max and current value
 94+ iterations : {
 95+ 'curr' : 0,
 96+ 'min' : 0,
 97+ 'max' : 0
 98+ },
 99+
 100+ // localized UI messages
 101+ localMessages : null,
 102+
 103+ setLocalNames : function( localMessages ) {
 104+ this.localMessages = localMessages;
 105+ },
 106+
 107+ formatMessage : function() {
 108+ var formatted = this.localMessages[ arguments[0] ];
 109+ var indexes = [];
 110+ var pos;
 111+ var j;
 112+ // going in reverse order is very important for the next for loop to be correct
 113+ for ( var i = arguments.length - 1; i > 0; i-- ) {
 114+ if ( ( pos = formatted.indexOf( '$' + i ) ) !== -1 ) {
 115+ indexes.push( pos );
 116+ }
 117+ }
 118+ for ( i = 0; i < indexes.length; i++ ) {
 119+ pos = indexes[i];
 120+ j = formatted.charAt( pos + 1 );
 121+ formatted = formatted.slice( 0, pos ) + arguments[j] + formatted.slice( pos + 2 );
 122+ }
 123+ return formatted;
 124+ },
 125+
 126+ mathLogBase : function( x, base ) {
 127+ return Math.log( x ) / Math.log( base );
 128+ },
 129+
 130+ showIframe : function( url ) {
 131+ var text = document.createTextNode( url );
 132+ var locElem = document.getElementById( 'wikisync_iframe_location' );
 133+ var iframe = document.getElementById( 'wikisync_iframe' );
 134+ if ( locElem.firstChild === null ) {
 135+ locElem.appendChild( text );
 136+ } else {
 137+ locElem.replaceChild( text, locElem.firstChild );
 138+ }
 139+ iframe.style.display = (url === '') ? 'none' : 'block';
 140+ iframe.src = url;
 141+ },
 142+
 143+ log : function( s, color, type ) {
 144+ var logContainer = document.getElementById( 'wikisync_remote_log' );
 145+ var span = document.createElement( 'SPAN' );
 146+ if ( typeof s === 'object' ) {
 147+ s = JSON.stringify( s );
 148+ }
 149+ if ( typeof type !== 'undefined' ) {
 150+ var b = document.createElement( 'B' );
 151+ b.appendChild( document.createTextNode( type + ': ' ) );
 152+ span.appendChild( b );
 153+ }
 154+ span.appendChild( document.createTextNode( s ) );
 155+ if ( typeof color !== 'undefined' ) {
 156+ span.style.color = color;
 157+ }
 158+ logContainer.appendChild( span );
 159+ logContainer.appendChild( document.createElement( 'HR' ) );
 160+ logContainer.scrollTop = logContainer.scrollHeight;
 161+ },
 162+
 163+ sourceLog : function( s, type ) {
 164+ var t = 'source';
 165+ if ( typeof type !== 'undefined' ) {
 166+ t += ' ' + type;
 167+ }
 168+ this.log( s, 'teal', t );
 169+ },
 170+
 171+ destinationLog : function( s, type ) {
 172+ var t = 'destination';
 173+ if ( typeof type !== 'undefined' ) {
 174+ t += ' ' + type;
 175+ }
 176+ this.log( s, 'maroon', t );
 177+ },
 178+
 179+ clearLog : function() {
 180+ document.getElementById( 'wikisync_remote_log' ).innerHTML = '';
 181+ return false;
 182+ },
 183+
 184+ error : function( s, type ) {
 185+ if ( typeof type !== 'undefined' ) {
 186+ this.log( s, 'red', type );
 187+ } else {
 188+ this.log( s, 'red' );
 189+ }
 190+ },
 191+
 192+ setDirection : function( eventObj ) {
 193+ eventObj.blur();
 194+ if ( this.directionToLocal = !this.directionToLocal ) {
 195+ eventObj.value = '<=';
 196+ } else {
 197+ eventObj.value = '=>';
 198+ }
 199+ return false;
 200+ },
 201+
 202+ setSyncFiles : function( eventObj ) {
 203+ eventObj.blur();
 204+ this.syncFiles = eventObj.checked;
 205+ return false;
 206+ },
 207+
 208+ remoteRootChange : function( eventObj ) {
 209+ var textNode = document.createTextNode( eventObj.value );
 210+ var wrr = document.getElementById( 'wikisync_remote_root' );
 211+ wrr.replaceChild( textNode , wrr.firstChild );
 212+ return false;
 213+ },
 214+
 215+ submitRemoteLogin : function( form ) {
 216+ this.remoteContext.wikiroot = form.remote_wiki_root.value;
 217+ this.setSyncFiles( form.ws_sync_files );
 218+ sajax_do_call( 'WikiSyncClient::remoteLogin',
 219+ [this.remoteContext.wikiroot, form.remote_wiki_user.value, form.remote_wiki_pass.value],
 220+ WikiSync.remoteLogin );
 221+ return false;
 222+ },
 223+
 224+ /*
 225+ * initializes everything in remoteContext except of wikiroot
 226+ */
 227+ setRemoteContext : function( login ) {
 228+ this.remoteContext.userid = login.userid;
 229+ this.remoteContext.username = login.username;
 230+ this.remoteContext.logintoken = login.token;
 231+ this.remoteContext.cookieprefix = login.cookieprefix;
 232+ this.remoteContext.sessionid = login.sessionid;
 233+ },
 234+
 235+ getResponseError : function( request ) {
 236+ return 'status=' + request.status + ', text=' + request.responseText;
 237+ },
 238+
 239+ /**
 240+ * enables or disables UI buttons
 241+ * arguments[0] - boolean true to disable selected buttons, false to enable
 242+ * (every unlisted button will be set to _inverse_ value)
 243+ * arguments[1..n] - string list of buttons to set to arguments[0] value
 244+ */
 245+ setButtons : function() {
 246+ var set = arguments[0] === true;
 247+ var ids = ['wikisync_submit_button', 'wikisync_direction_button', 'wikisync_synchronization_button', 'ws_sync_files' ];
 248+ var button;
 249+ for ( var i = 0; i < ids.length; i++ ) {
 250+ var current = !set;
 251+ for ( var j = 1; j < arguments.length; j++ ) {
 252+ if ( arguments[j] === ids[i] ) {
 253+ current = !current;
 254+ break;
 255+ }
 256+ }
 257+ button = document.getElementById( ids[i] );
 258+ button.disabled = current;
 259+ }
 260+ },
 261+
 262+ /*
 263+ * @param request.responsetext - login "final" response
 264+ */
 265+ remoteLogin : function( request ) {
 266+ // {{{ switch the context
 267+ if ( typeof this._WikiSync === 'undefined' ) {
 268+ return WikiSync.remoteLogin.call( WikiSync, request );
 269+ }
 270+ // switch the context }}}
 271+ var syncButton = document.getElementById( 'wikisync_synchronization_button' );
 272+ syncButton.disabled = true;
 273+ if ( request.status != 200 ) {
 274+ this.error( 'Invalid AJAX response from local wiki server in WikiSync.remoteLogin, ' + this.getResponseError( request ) );
 275+ return;
 276+ }
 277+// this.log( request.responseText );
 278+ try {
 279+ var loginResult = JSON.parse( request.responseText );
 280+ } catch (e) {
 281+ this.error( 'Local wiki server returned invalid JSON data in WikiSync.remoteLogin: '+e );
 282+ return;
 283+ }
 284+ if ( loginResult.ws_status != '1' ) {
 285+ this.error( loginResult.ws_code + ':' + loginResult.ws_msg );
 286+ return;
 287+ }
 288+ // logged in
 289+ this.log( loginResult.ws_msg );
 290+ syncButton.disabled = false;
 291+ this.setRemoteContext( loginResult );
 292+ },
 293+
 294+ retryAJAX : function( AJAXresult ) {
 295+ return AJAXresult.ws_status != '1' &&
 296+ confirm( this.formatMessage( 'last_op_error', AJAXresult.ws_code, AJAXresult.ws_msg ) )
 297+ },
 298+
 299+ _localAPIget : function( APIparams, operation ) {
 300+ sajax_do_call( 'WikiSyncClient::localAPIget',
 301+ [ JSON.stringify( APIparams ) ],
 302+ function() {
 303+ var r = WikiSync.getAJAXresult.call( WikiSync, arguments[0] );
 304+ if ( (typeof r.ws_auto_retry !== 'undefined') || WikiSync.retryAJAX.call( WikiSync, r ) ) {
 305+ r = null; // IE closure purge
 306+ WikiSync.log( 'retrying last call to _localAPIget' );
 307+ WikiSync._localAPIget.call( WikiSync, APIparams, operation );
 308+ return;
 309+ }
 310+ WikiSync.AJAXresult[operation.opcode] = r;
 311+ r = null; // IE closure purge
 312+ WikiSync[operation.fname].call( WikiSync, operation );
 313+ }
 314+ );
 315+ },
 316+
 317+ /**
 318+ * call WikiSync client API method via AJAX
 319+ * @param method PHP method name
 320+ * @param clientParams params to pass to PHP client method
 321+ * @param operation callback in form { 'fname': , 'opcode', ... } to call on AJAX event completion
 322+ */
 323+ _wsAPI : function( method, clientParams, operation ) {
 324+ sajax_do_call( 'WikiSyncClient::' + method,
 325+ [ JSON.stringify( this.remoteContext ), JSON.stringify( clientParams ) ],
 326+ function() {
 327+ var r = WikiSync.getAJAXresult.call( WikiSync, arguments[0] );
 328+ if ( (typeof r.ws_auto_retry !== 'undefined') || WikiSync.retryAJAX.call( WikiSync, r ) ) {
 329+ r = null; // IE closure purge
 330+ WikiSync.log( 'retrying last call to ' + method );
 331+ WikiSync.wsAPI.call( WikiSync, method, clientParams, operation );
 332+ return;
 333+ }
 334+ WikiSync.AJAXresult[operation.opcode] = r;
 335+ r = null; // IE closure purge
 336+ WikiSync[operation.fname].call( WikiSync, operation );
 337+ }
 338+ );
 339+ },
 340+
 341+ wsAPI : function( method, clientParams, operation ) {
 342+ clientParams.direction_to_local = this.directionToLocal;
 343+ this._wsAPI( method, clientParams, operation );
 344+ },
 345+
 346+ sourceAPIget : function( APIparams, operation ) {
 347+ operation.opcode = 'src_'+operation.opcode;
 348+ if ( this.directionToLocal ) {
 349+ return this._wsAPI( 'remoteAPIget', APIparams, operation );
 350+ } else {
 351+ return this._localAPIget( APIparams, operation );
 352+ }
 353+ },
 354+
 355+ destinationAPIget : function( APIparams, operation ) {
 356+ operation.opcode = 'dst_' + operation.opcode;
 357+ if ( this.directionToLocal ) {
 358+ return this._localAPIget( APIparams, operation );
 359+ } else {
 360+ return this._wsAPI( 'remoteAPIget', APIparams, operation );
 361+ }
 362+ },
 363+
 364+ getAJAXresult : function( request ) {
 365+ var AJAXres = { 'ws_status' : '0', 'ws_code' : 'uninitialized', 'ws_msg' : 'uninitialized' };
 366+ if ( request.status != 200 ) {
 367+ AJAXres.ws_code = 'http';
 368+ AJAXres.ws_msg = 'Request error ' + this.getResponseError( request );
 369+ return AJAXres;
 370+ }
 371+// this.log( 'getAJAXresult:' + request.responseText );
 372+ try {
 373+ AJAXres = JSON.parse( request.responseText );
 374+ } catch (e) {
 375+// this.error( 'Local wiki server returned invalid JSON data in WikiSync.getAJAXresult: ' + request.responseText );
 376+ AJAXres.ws_code = 'ajax';
 377+ AJAXres.ws_msg = request.responseText;
 378+ }
 379+ return AJAXres;
 380+ },
 381+
 382+ isAJAXresult : function() {
 383+ var found = 0;
 384+ for ( var i = 0; i < arguments.length; i++ ) {
 385+ if ( typeof this.AJAXresult[ arguments[i] ] !== 'undefined' ) {
 386+ found++;
 387+ }
 388+ }
 389+ return found == arguments.length;
 390+ },
 391+
 392+ errorDefaultAction : function() {
 393+ this.syncPercents.reset();
 394+ this.filesPercents.reset();
 395+ this.showIframe( '' );
 396+ // enable all but synchronization buttons
 397+ this.setButtons( true, 'wikisync_synchronization_button' );
 398+ },
 399+
 400+ assertAJAXerrors : function() {
 401+ var result = false;
 402+ for ( var key in this.AJAXresult ) {
 403+ if ( this.AJAXresult[key].ws_status != '1' ) {
 404+ this.error( this.AJAXresult[key].ws_code + ': ' + this.AJAXresult[key].ws_msg, key );
 405+ delete this.AJAXresult[key];
 406+ result = true;
 407+ }
 408+ }
 409+ if ( result ) {
 410+ this.errorDefaultAction();
 411+ }
 412+ return result;
 413+ },
 414+
 415+ popAJAXresult : function( key, nested_props ) {
 416+ var r = this.AJAXresult[key];
 417+ delete this.AJAXresult[key]; // clear events list
 418+ if ( typeof nested_props === 'undefined' ) {
 419+ return r;
 420+ }
 421+ if ( typeof nested_props === 'string' ) {
 422+ return r[nested_props];
 423+ }
 424+ for ( var i = 0; i < nested_props.length; i++ ) {
 425+ r = r[nested_props[i]];
 426+ }
 427+ return r;
 428+ },
 429+
 430+ getImportToken : function( operation ) {
 431+ switch ( operation.opcode ) {
 432+ case 'start' :
 433+ // get sample page title for token importing
 434+ var APIparams = {
 435+ 'action' : 'query',
 436+ 'format' : 'json',
 437+ 'list' : 'allpages',
 438+ 'aplimit' : '1'
 439+ };
 440+ var params = {
 441+ 'fname' : 'getImportToken',
 442+ 'opcode' : 'get_import_token'
 443+ };
 444+ if ( typeof operation.next_title !== 'undefined' ) {
 445+ if ( operation.next_title === '{}' ) {
 446+ this.error( 'Cannot get valid title for import token' );
 447+ return;
 448+ }
 449+ APIparams.apfrom = operation.next_title;
 450+ }
 451+ // will fire AJAX event 'dst_get_import_token' (based on params)
 452+ this.destinationAPIget( APIparams, params );
 453+ return;
 454+ case 'dst_get_import_token' :
 455+ // get import token for destination wiki
 456+ this.destinationLog( this.AJAXresult[operation.opcode] );
 457+ if ( this.assertAJAXerrors() ) { return; /* there were AJAX errors, no go */ }
 458+ var result = this.popAJAXresult( operation.opcode );
 459+ var sampleTitle = result.query.allpages[0].title;
 460+ var nextTitle = '{}';
 461+ if ( typeof result['query-continue'] !== 'undefined' ) {
 462+ nextTitle = result['query-continue'].allpages.apfrom;
 463+ }
 464+ this.destinationLog( sampleTitle, 'sample title' );
 465+ var APIparams = {
 466+ 'action' : 'query',
 467+ 'format' : 'json',
 468+ 'prop' : 'info',
 469+ 'intoken' : 'import',
 470+ 'titles' : sampleTitle
 471+ };
 472+ var params = {
 473+ 'fname' : 'getImportToken',
 474+ 'opcode' : 'set_import_token',
 475+ 'next_title' : nextTitle
 476+ };
 477+ // will fire AJAX event 'dst_set_import_token' (based on params)
 478+ this.destinationAPIget( APIparams, params );
 479+ return;
 480+ case 'dst_set_import_token' :
 481+ // set import token for destination wiki
 482+ this.destinationLog( this.AJAXresult[operation.opcode] );
 483+ if ( this.assertAJAXerrors() ) { return; /* there were AJAX errors, no go */ }
 484+ var result = this.popAJAXresult( operation.opcode );
 485+ var pages = result.query.pages;
 486+ for ( var i in pages ) {
 487+ if ( typeof pages[i].importtoken === 'undefined' ) {
 488+ if ( typeof pages[i].invalid !== 'undefined' ) {
 489+ // current title is invalid thus offers no token, try next title
 490+ var params = {
 491+ 'opcode': 'start',
 492+ 'next_title': operation.next_title
 493+ };
 494+ this.getImportToken( params );
 495+ return;
 496+ }
 497+ this.error( 'Cannot get import token for destination wiki' );
 498+ return;
 499+ }
 500+ this.dstImportToken = pages[i].importtoken;
 501+ break;
 502+ }
 503+ if ( typeof result.warnings !== 'undefined' ) {
 504+ this.error( result.warnings );
 505+ return;
 506+ }
 507+ this.destinationLog( this.dstImportToken, 'import token' );
 508+ var params = {
 509+ 'fname' : 'synchronize',
 510+ 'opcode' : 'xml_chunk',
 511+ 'startid' : '' + this.srcSyncId
 512+ };
 513+ this.syncPercents.display( { 'desc' : this.formatMessage( 'revision', this.srcSyncId ), 'curr' : this.srcSyncId, 'min' : this.srcSyncId, 'max' : this.srcLastId } );
 514+ this.synchronize( params );
 515+ return;
 516+ }
 517+ },
 518+
 519+ /**
 520+ * transfer one file in blocks of specified length
 521+ */
 522+ transferFile : function( operation ) {
 523+ switch ( operation.opcode ) {
 524+ case 'start_upload' :
 525+ this.currFileOffset = 0;
 526+ if ( typeof operation.file_idx !== 'undefined' ) {
 527+ this.fileListIdx = operation.file_idx;
 528+ }
 529+ // set progress title
 530+ this.filesPercents.display( { 'desc' : this.fileList[this.fileListIdx].title } );
 531+ this.transferFile( { 'opcode' : 'get_block', 'offset' : 0 } );
 532+ return;
 533+ case 'get_block' :
 534+ // transfer the files, one by one, in chunks
 535+ if ( typeof operation.offset !== 'undefined' ) {
 536+ this.currFileOffset = operation.offset;
 537+ }
 538+ var clientParams = {
 539+ 'title' : this.fileList[this.fileListIdx].title,
 540+ 'timestamp' : this.fileList[this.fileListIdx].timestamp, // timestamp of requested archived file
 541+ 'offset' : this.currFileOffset, // chunk's start position
 542+ // please do not use larger blocklen value because it can cause php memory exhaust errors and timeouts
 543+ 'blocklen' : 1024 * 1024
 544+ };
 545+ var nextOp = {
 546+ 'fname' : 'transferFile',
 547+ 'opcode' : 'file_block_result',
 548+ };
 549+ this.wsAPI( 'transferFileBlock', clientParams, nextOp );
 550+ return;
 551+ case 'file_block_result' :
 552+ this.log( this.AJAXresult[operation.opcode] );
 553+ if ( this.assertAJAXerrors() ) { return; /* there were AJAX errors, no go */ }
 554+ var result = this.popAJAXresult( operation.opcode );
 555+ this.currFileOffset += result.numread;
 556+ this.fileListSize += result.numread;
 557+ // set progress current position
 558+ this.filesPercents.display( { 'curr' : this.fileListSize } );
 559+ if ( typeof result.done === 'undefined' ) {
 560+ // transfer next chunk
 561+ this.transferFile( { 'opcode' : 'get_block' } );
 562+ return;
 563+ }
 564+ // all the chunks of current file were transferred succesfully, upload the file
 565+ var clientParams = {
 566+ 'file_name' : result.chunk_fname,
 567+ 'file_timestamp' : this.fileList[this.fileListIdx].timestamp,
 568+ 'file_size' : this.fileList[this.fileListIdx].size // simple bugcheck for temporary file consistency
 569+ };
 570+ var nextOp = {
 571+ 'fname' : 'transferFile',
 572+ 'opcode' : 'local_upload_result',
 573+ };
 574+ this.wsAPI( 'uploadLocalFile', clientParams, nextOp );
 575+ return;
 576+ case 'local_upload_result' :
 577+ this.log( this.AJAXresult[operation.opcode] );
 578+ if ( this.assertAJAXerrors() ) { return; /* there were AJAX errors, no go */ }
 579+ var result = this.popAJAXresult( operation.opcode );
 580+ // simple bugcheck for temporary file consistency
 581+ if ( result.tmp_file_size !== this.fileList[this.fileListIdx].size ) {
 582+ alert( this.formatMessage( 'file_size_mismatch', result.chunk_fpath, result.tmp_file_size, this.fileList[this.fileListIdx].size, this.fileList[this.fileListIdx].title ) );
 583+ }
 584+ this.fileListIdx++;
 585+ if ( this.fileListIdx >= this.fileList.length ) {
 586+ // the file list is over; try to synchronize next xml chunk
 587+ if ( this.xmlContinueStartId === null ) {
 588+ // synchronization is complete
 589+ this.synchronize( { 'opcode' : 'success' } );
 590+ return;
 591+ }
 592+ var nextOp = {
 593+ 'fname' : 'synchronize',
 594+ 'opcode' : 'xml_chunk',
 595+ 'startid' : '' + this.xmlContinueStartId
 596+ };
 597+ this.synchronize( nextOp );
 598+ return;
 599+ }
 600+ // upload next file in the list
 601+ this.transferFile( { 'opcode' : 'start_upload' } );
 602+ return;
 603+ }
 604+ },
 605+
 606+ /**
 607+ * update new files currently available in chunk (if any)
 608+ */
 609+ updateNewFiles : function( operation ) {
 610+ switch ( operation.opcode ) {
 611+ case 'new_files_result' :
 612+ this.log( this.AJAXresult[operation.opcode] );
 613+ if ( this.assertAJAXerrors() ) { return; /* there were AJAX errors, no go */ }
 614+ var result = this.popAJAXresult( operation.opcode );
 615+ // fileList will contain the list of only new files in chunk (which has to be updated)
 616+ this.fileList = [];
 617+ if ( typeof result.new_files === 'undefined' ) {
 618+ // no files to update
 619+ if ( this.xmlContinueStartId === null ) {
 620+ // synchronization is complete
 621+ this.synchronize( { 'opcode' : 'success' } );
 622+ return;
 623+ }
 624+ // try to synchronize next chunk
 625+ var params = {
 626+ 'fname' : 'synchronize',
 627+ 'opcode' : 'xml_chunk',
 628+ 'startid' : '' + this.xmlContinueStartId
 629+ };
 630+ this.synchronize( params );
 631+ return;
 632+ }
 633+ this.fileList = result.new_files;
 634+ this.fileListSize = 0;
 635+ // total size of all files in the list, used for progress indicator
 636+ var fileListTotalSize = 0;
 637+ for ( var i = 0; i < this.fileList.length; i++ ) {
 638+ fileListTotalSize += this.fileList[i].size;
 639+ }
 640+ this.filesPercents.setVisibility( true );
 641+ // set progress dimenstions
 642+ this.filesPercents.display( { 'curr' : 0, 'min' : 0, 'max' : fileListTotalSize } );
 643+ this.transferFile( { 'opcode' : 'start_upload', 'file_idx': 0 } );
 644+ return;
 645+ }
 646+ },
 647+
 648+ /**
 649+ * synchronize xml chunks in blocks (optionally with passing through file update)
 650+ */
 651+ synchronize : function( operation ) {
 652+ switch ( operation.opcode ) {
 653+ case 'start' :
 654+ this.srcSyncId = operation.revid;
 655+ this.showIframe( this.srcWikiRoot + '/index.php?oldid=' + operation.revid );
 656+ if ( !confirm( this.formatMessage( 'synchronization_confirmation', this.srcWikiRoot, this.dstWikiRoot, operation.revid ) ) ) {
 657+ this.log( 'Operation was cancelled' );
 658+ this.syncPercents.reset();
 659+ this.filesPercents.reset();
 660+ // enable all buttons
 661+ this.setButtons( true );
 662+ return;
 663+ }
 664+ this.getImportToken( operation ); // will use operation.opcode : 'start'
 665+ this.log( 'Trying to synchronize from revision ' + operation.revid );
 666+ return;
 667+ case 'xml_chunk' :
 668+ var clientParams = {
 669+ 'startid' : parseInt( operation.startid ),
 670+ // please do not use larger value because it can cause memory exhaust errors and php timeouts
 671+ 'limit' : 10,
 672+ 'dst_import_token' : this.dstImportToken
 673+ };
 674+ var nextOp = {
 675+ 'fname' : 'synchronize',
 676+ 'opcode' : 'xml_chunk_result'
 677+ };
 678+ this.wsAPI( 'syncXMLchunk', clientParams, nextOp );
 679+ return;
 680+ case 'xml_chunk_result' :
 681+ /* recieve source or destination revisions list (single) */
 682+ this.log( this.AJAXresult[operation.opcode] );
 683+ if ( this.assertAJAXerrors() ) { return; /* there were AJAX errors, no go */ }
 684+ var result = this.popAJAXresult( operation.opcode );
 685+ this.xmlContinueStartId = (typeof result.ws_continue_startid) === 'undefined' ? null : result.ws_continue_startid;
 686+ if ( this.xmlContinueStartId === null ) {
 687+ this.syncPercents.display( { 'desc' : '', 'curr' : 'max' } );
 688+ } else {
 689+ this.syncPercents.display( { 'desc' : this.formatMessage( 'revision', this.xmlContinueStartId ), 'curr' : this.xmlContinueStartId } );
 690+ }
 691+ if ( this.syncFiles && typeof result.files !== 'undefined' ) {
 692+ var clientParams = {
 693+ // result.files contains the list of all chunk files (some might be already updated)
 694+ 'chunk_files' : result.files
 695+ };
 696+ var nextOp = {
 697+ 'fname' : 'updateNewFiles',
 698+ 'opcode' : 'new_files_result'
 699+ };
 700+ this.wsAPI( 'findNewFiles', clientParams, nextOp );
 701+ return;
 702+ }
 703+ if ( this.xmlContinueStartId === null ) {
 704+ // synchronization is complete
 705+ this.synchronize( { 'opcode' : 'success' } );
 706+ return;
 707+ }
 708+ // try to synchronize next chunk
 709+ var params = {
 710+ 'fname' : 'synchronize',
 711+ 'opcode' : 'xml_chunk',
 712+ 'startid' : '' + this.xmlContinueStartId
 713+ };
 714+ this.synchronize( params );
 715+ return;
 716+ case 'success' :
 717+ this.filesPercents.setVisibility( false );
 718+ this.syncPercents.display( { 'desc' : '', 'curr' : 'max' } );
 719+ alert( this.formatMessage( 'synchronization_success' ) );
 720+ // enable all buttons
 721+ this.setButtons( true );
 722+ return;
 723+ }
 724+ },
 725+
 726+ /**
 727+ * find least common revid in destination wiki taken from source wiki via binary search
 728+ */
 729+ findCommonRev : function( operation ) {
 730+ if ( typeof operation === 'undefined' || typeof operation.opcode === 'undefined' ) {
 731+ this.error( 'Bug: No operation.opcode in WikiSync.getLastSrcRev' );
 732+ return;
 733+ }
 734+ switch ( operation.opcode ) {
 735+ case 'start' :
 736+ var len = this.srcHiId - this.srcLoId;
 737+ var center = (len > 1) ? ( len ) / 2 : 1;
 738+ this.srcMidId = Math.floor( this.srcLoId + center );
 739+ var eventCbParams = {
 740+ 'fname' : 'getSrcRev',
 741+ 'opcode' : 'start',
 742+ 'dir' : 'newer', // look-up top
 743+ 'startid' : '' + this.srcMidId
 744+ };
 745+ this.getSrcRev( eventCbParams );
 746+ return;
 747+ case 'search_in_dst' :
 748+ // look for match of this.srcRev in destination wiki
 749+ var APIparams = {
 750+ 'action' : 'similarrev',
 751+ 'format' : 'json',
 752+ 'limit' : '100',
 753+ 'usertext' : this.srcRev.usertext,
 754+ 'timestamp' : this.srcRev.timestamp,
 755+ };
 756+ var eventCbParams = {
 757+ 'fname' : 'findCommonRev',
 758+ 'opcode' : 'search_results',
 759+ 'textlen' : '' + this.srcRev.textlen
 760+ };
 761+ this.destinationAPIget( APIparams, eventCbParams );
 762+ return;
 763+ case 'dst_search_results' :
 764+ /* recieve source "middle" revision search results */
 765+ this.destinationLog( this.AJAXresult[operation.opcode], 'search results' );
 766+ if ( this.assertAJAXerrors() ) { return; /* there were AJAX errors, no go */ }
 767+ var dstRevs = this.popAJAXresult( operation.opcode, ['query', 'similarrev'] );
 768+ // also look for matching source textlen in destination wiki (for a better match and less probability of screw-up)
 769+ var matches = 0;
 770+ for ( var i = 0; i < dstRevs.length; i++ ) {
 771+ if ( dstRevs[i].textlen === operation.textlen ) {
 772+ matches++;
 773+ }
 774+ }
 775+ var prevLen = this.srcHiId - this.srcLoId;
 776+ this.syncPercents.display( { 'curr' : 'next' } );
 777+ if ( matches > 0 ) {
 778+ if ( matches > 1 ) {
 779+ this.destinationLog( 'Warning: more than one match for source revision id' );
 780+ }
 781+ // source "middle" revision has a match in destination wiki, look-up top
 782+ this.srcLoId = this.srcMidId;
 783+ } else {
 784+ // source "middle" revision has no match in destination wiki, look-up bottom
 785+ this.srcHiId = this.srcMidId;
 786+ }
 787+ var currLen = this.srcHiId - this.srcLoId;
 788+ if ( currLen <= 0 || prevLen === currLen ) {
 789+ // binary search is complete
 790+ this.syncPercents.display( { 'desc' : '', 'curr' : 'max' } );
 791+ if ( this.srcHiId === this.srcLastId && matches > 0 ) {
 792+ alert( this.formatMessage( 'already_synchronized' ) );
 793+ // enable all buttons
 794+ this.setButtons( true );
 795+ return;
 796+ }
 797+ this.log( 'Synchronizing from ' + this.srcHiId );
 798+ this.synchronize( { 'opcode' : 'start', 'revid' : '' + this.srcHiId } );
 799+ return;
 800+ }
 801+ var eventCbParams = {
 802+ 'fname' : 'findCommonRev',
 803+ 'opcode' : 'start'
 804+ };
 805+ this.findCommonRev( eventCbParams );
 806+ return;
 807+ }
 808+ },
 809+
 810+ /**
 811+ * get (last and first in parallel) or any single (with top or down look-up) source wiki revision ids
 812+ */
 813+ getSrcRev : function( operation ) {
 814+ if ( typeof operation === 'undefined' || typeof operation.opcode === 'undefined' ) {
 815+ this.error( 'Bug: No operation.opcode in WikiSync.getLastSrcRev' );
 816+ return;
 817+ }
 818+ switch ( operation.opcode ) {
 819+ case 'start' :
 820+ var APIparams = {
 821+ 'action' : 'revisionhistory',
 822+ 'format' : 'json',
 823+ 'dir' : 'older',
 824+ 'limit' : '1'
 825+ };
 826+ var eventCbParams = {
 827+ 'fname': 'getSrcRev',
 828+ 'opcode': 'rev'
 829+ };
 830+ if ( typeof operation.dir !== 'undefined' ) {
 831+ APIparams.dir = operation.dir;
 832+ }
 833+ if ( typeof operation.startid !== 'undefined' ) {
 834+ // start from selected revision instead of first / last one
 835+ APIparams.startid = operation.startid;
 836+ eventCbParams.startid = operation.startid;
 837+ // look up down or top
 838+ eventCbParams.opcode += APIparams.dir == 'older' ? '_down' : '_top';
 839+ } else {
 840+ // will get first and last revisions in parallel
 841+ eventCbParams.opcode += APIparams.dir === 'older' ? '_last' : '_first';
 842+ }
 843+ this.sourceAPIget( APIparams, eventCbParams );
 844+ return;
 845+ case 'src_rev_down' :
 846+ case 'src_rev_top' :
 847+ /* recieve single source revision (top or down look-up) */
 848+ this.sourceLog( this.AJAXresult[operation.opcode] );
 849+ if ( this.assertAJAXerrors() ) { return; /* there were AJAX errors, no go */ }
 850+ this.srcRev = this.popAJAXresult( operation.opcode, ['query', 'revisionhistory', 0] );
 851+ var correctedId = parseInt( this.srcRev.revid );
 852+ if ( this.srcMidId !== correctedId ) {
 853+ this.sourceLog( 'Warning: closest match for calculated source revid = ' + this.srcMidId + ' is actual revid = ' + correctedId );
 854+ }
 855+ // this possible correction requires additional check when binary search is complete
 856+ this.srcMidId = correctedId;
 857+ this.findCommonRev( { 'opcode' : 'search_in_dst' } );
 858+ return;
 859+ case 'src_rev_first' :
 860+ case 'src_rev_last' :
 861+ /* recieve source and destination revisions lists (both) */
 862+ if ( !this.isAJAXresult( 'src_rev_first', 'src_rev_last' ) ) { return; /* not all of fired AJAX events are completed */ }
 863+ this.sourceLog( this.AJAXresult['src_rev_first'], 'first revision' );
 864+ this.sourceLog( this.AJAXresult['src_rev_last'], 'last revision' );
 865+ if ( this.assertAJAXerrors() ) { return; /* there were AJAX errors, no go */ }
 866+ this.srcFirstId = this.srcLoId = parseInt( this.popAJAXresult( 'src_rev_first', ['query', 'revisionhistory', 0, 'revid'] ) );
 867+ this.srcLastId = this.srcHiId = parseInt( this.popAJAXresult( 'src_rev_last', ['query', 'revisionhistory', 0, 'revid'] ) );
 868+ // uncomment next line for "live" debugging
 869+ // this.srcLastId = this.srcHiId = 75054;
 870+ this.syncPercents.display( { 'desc' : this.localMessages['diff_search'], 'curr' : 0, 'min' : 0, 'max' : Math.ceil( this.mathLogBase( this.srcLastId - this.srcFirstId, 2 ) ) } );
 871+ this.findCommonRev( { 'opcode' : 'start' } );
 872+ return;
 873+ }
 874+ },
 875+
 876+ /**
 877+ * "Synchronize" button click handler
 878+ */
 879+ process : function() {
 880+ this.syncPercents = new WikiSyncPercentsIndicator( 'wikisync_xml_percents' );
 881+ this.filesPercents = new WikiSyncPercentsIndicator( 'wikisync_files_percents' );
 882+ this.syncPercents.setVisibility( true );
 883+ this.showIframe( '' );
 884+ if ( wgServer.indexOf( this.remoteContext.wikiroot ) !== -1 ||
 885+ this.remoteContext.wikiroot.indexOf( wgServer ) !== -1 ) {
 886+ alert( this.formatMessage( 'sync_to_itself' ) );
 887+ return;
 888+ }
 889+ // setup direction of synchronization
 890+ if ( this.directionToLocal ) {
 891+ this.srcWikiRoot = this.remoteContext.wikiroot;
 892+ this.dstWikiRoot = wgServer;
 893+ } else {
 894+ this.srcWikiRoot = wgServer;
 895+ this.dstWikiRoot = this.remoteContext.wikiroot;
 896+ }
 897+ // disable all buttons
 898+ this.setButtons( false );
 899+ /* get first and last source revision in parallel */
 900+ this.getSrcRev( { 'opcode' : 'start' } );
 901+ this.getSrcRev( { 'opcode' : 'start', 'dir' : 'newer' } );
 902+ return false;
 903+ }
 904+
 905+}
Property changes on: trunk/extensions/WikiSync/WikiSync.js
___________________________________________________________________
Added: svn:eol-style
1906 + native
Index: trunk/extensions/WikiSync/WikiSync.alias.php
@@ -0,0 +1,15 @@
 2+<?php
 3+/**
 4+ * Aliases for special pages
 5+ *
 6+ * @file
 7+ * @ingroup Extensions
 8+ */
 9+
 10+$aliases = array();
 11+
 12+/** English */
 13+$aliases['en'] = array(
 14+ 'WikiSync' => array( 'WikiSync' ),
 15+);
 16+
Property changes on: trunk/extensions/WikiSync/WikiSync.alias.php
___________________________________________________________________
Added: svn:eol-style
117 + native

Follow-up revisions

RevisionCommit summaryAuthorDate
r75839Followup r75817, fixup api code a little bitreedy23:02, 1 November 2010

Comments

#Comment by Reedy (talk | contribs)   19:41, 1 November 2010

Please set your autoprops, and then fix the svn properties of these files

#Comment by Reedy (talk | contribs)   22:49, 1 November 2010

Ignore that, sorry.

#Comment by Reedy (talk | contribs)   23:03, 1 November 2010

pear/JSON.php exists in trunk/phase3/includes/json/Services_JSON.php

Just FYI :)

Status & tagging log