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 |
1 | 97 | + 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×tamp=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×tamp=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×tamp=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 |
1 | 607 | + 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'=>'<=', '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 |
1 | 238 | + 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 |
1 | 884 | + 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 |
1 | 100 | + 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 |
1 | 154 | + 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 |
1 | 808 | + 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 | + "'ß'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 |
1 | 1252 | + 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 |
1 | 25 | + 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 |
1 | 218 | + 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 |
1 | 72 | + 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 |
1 | 311 | + 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 |
1 | 906 | + 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 |
1 | 17 | + native |