PageRenderTime 22ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/extensions/WikiSync/WikiSync.php

https://github.com/ChuguluGames/mediawiki-svn
PHP | 363 lines | 238 code | 33 blank | 92 comment | 29 complexity | a3eb357cf10b8a5c6d4f43f73dbd2788 MD5 | raw file
  1. <?php
  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.3.2
  32. * @link http://www.mediawiki.org/wiki/Extension:WikiSync
  33. * @author Dmitriy Sintsov <questpc@rambler.ru>
  34. * @addtogroup Extensions
  35. */
  36. if ( !defined( 'MEDIAWIKI' ) ) {
  37. die( "This file is a part of MediaWiki extension.\n" );
  38. }
  39. $wgExtensionCredits['specialpage'][] = array(
  40. 'path' => __FILE__,
  41. 'name' => 'WikiSync',
  42. 'author' => 'QuestPC',
  43. 'url' => 'http://www.mediawiki.org/wiki/Extension:WikiSync',
  44. 'descriptionmsg' => 'wikisync-desc',
  45. );
  46. $dir = dirname(__FILE__);
  47. $wgExtensionMessagesFiles['WikiSync'] = $dir . '/WikiSync.i18n.php';
  48. $wgExtensionAliasesFiles['WikiSync'] = $dir . '/WikiSync.alias.php';
  49. $wgSpecialPages['WikiSync'] = 'WikiSyncPage';
  50. $wgSpecialPageGroups['WikiSync'] = 'pagetools';
  51. if ( !isset( $wgAutoloadClasses['FormatJson'] ) ) {
  52. // for MediaWiki 1.15.5
  53. class FormatJson {
  54. /**
  55. * Returns the JSON representation of a value.
  56. *
  57. * @param $value Mixed: the value being encoded. Can be any type except a resource.
  58. * @param $isHtml Boolean
  59. *
  60. * @return string
  61. */
  62. public static function encode( $value, $isHtml = false ) {
  63. // Some versions of PHP have a broken json_encode, see PHP bug
  64. // 46944. Test encoding an affected character (U+20000) to
  65. // avoid this.
  66. if ( !function_exists( 'json_encode' ) || $isHtml || strtolower( json_encode( "\xf0\xa0\x80\x80" ) ) != '\ud840\udc00' ) {
  67. $json = new Services_JSON();
  68. return $json->encode( $value, $isHtml );
  69. } else {
  70. return json_encode( $value );
  71. }
  72. }
  73. /**
  74. * Decodes a JSON string.
  75. *
  76. * @param $value String: the json string being decoded.
  77. * @param $assoc Boolean: when true, returned objects will be converted into associative arrays.
  78. *
  79. * @return Mixed: the value encoded in json in appropriate PHP type.
  80. * Values true, false and null (case-insensitive) are returned as true, false
  81. * and &null; respectively. &null; is returned if the json cannot be
  82. * decoded or if the encoded data is deeper than the recursion limit.
  83. */
  84. public static function decode( $value, $assoc = false ) {
  85. if ( !function_exists( 'json_decode' ) ) {
  86. $json = new Services_JSON();
  87. $jsonDec = $json->decode( $value );
  88. if( $assoc ) {
  89. $jsonDec = wfObjectToArray( $jsonDec );
  90. }
  91. return $jsonDec;
  92. } else {
  93. return json_decode( $value, $assoc );
  94. }
  95. }
  96. }
  97. }
  98. WikiSyncSetup::init();
  99. class WikiSyncSetup {
  100. const COOKIE_EXPIRE_TIME = 2592000; // 60 * 60 * 24 * 30; see also WikiSync.js, WikiSync.cookieExpireTime
  101. # please never change next value in between the synchronizations
  102. # otherwise null revisions created by ImportReporter::reportPage
  103. # will not be skipped, thus fake synchronizations might occur
  104. const WIKISYNC_BOT_NAME = 'WikiSyncBot';
  105. static $remote_wiki_user = self::WIKISYNC_BOT_NAME;
  106. static $report_null_revisions = false;
  107. # {{{ changable in LocalSettings.php :
  108. # debug mode (do not erase temporary XML dump files)
  109. static $debug_mode = false;
  110. static $remote_wiki_root = 'http://www.mediawiki.org/w';
  111. static $proxy_address = ''; # 'http://10.0.0.78:3128';
  112. # which user groups can synchronize from remote to local
  113. static $rtl_access_groups = array( 'user' );
  114. # which user groups can synchronize from local to remote
  115. static $ltr_access_groups = array( 'sysop', 'bureaucrat' );
  116. # }}}
  117. # {{{ decoded local proxy settings
  118. static $proxy_host = '';
  119. static $proxy_port = '';
  120. static $proxy_user = '';
  121. static $proxy_pass = '';
  122. # }}}
  123. static $version = '0.3.2'; // version of extension
  124. static $ExtDir; // filesys path with windows path fix
  125. static $ScriptPath; // apache virtual path
  126. static $user; // current User
  127. static $cookie_prefix; // an extension's cookie prefix for current User
  128. static $response; // an extension's WebResponse
  129. const JS_MSG_PREFIX = 'wikisync_js_';
  130. static $jsMessages = array(
  131. 'last_op_error',
  132. 'synchronization_confirmation',
  133. 'synchronization_success',
  134. 'already_synchronized',
  135. 'sync_to_itself',
  136. 'diff_search',
  137. 'revision',
  138. 'file_size_mismatch',
  139. 'invalid_scheduler_time',
  140. 'scheduler_countdown',
  141. 'sync_start_ltr',
  142. 'sync_start_rtl',
  143. 'sync_end_ltr',
  144. 'sync_end_rtl'
  145. );
  146. static function init() {
  147. global $wgScriptPath;
  148. global $wgAutoloadClasses;
  149. global $wgAjaxExportList;
  150. global $wgAPIModules;
  151. global $wgHooks;
  152. self::$ExtDir = str_replace( "\\", "/", dirname( __FILE__ ) );
  153. $top_dir = explode( '/', self::$ExtDir );
  154. $top_dir = array_pop( $top_dir );
  155. self::$ScriptPath = $wgScriptPath . '/extensions' . ( ( $top_dir == 'extensions' ) ? '' : '/' . $top_dir );
  156. if ( !isset( $wgAutoloadClasses['_QXML'] ) ) {
  157. $wgAutoloadClasses['_QXML'] = self::$ExtDir . '/WikiSyncQXML.php';
  158. }
  159. if ( !isset( $wgAutoloadClasses['FormatJson'] ) ) {
  160. $wgAutoloadClasses['FormatJson'] = self::$ExtDir . '/WikiSync.php';
  161. }
  162. $wgAutoloadClasses['Snoopy'] = self::$ExtDir . '/Snoopy/Snoopy.class.php';
  163. $wgAutoloadClasses['WikiSyncSetup'] = self::$ExtDir . '/WikiSync.php';
  164. $wgAutoloadClasses['WikiSnoopy'] =
  165. $wgAutoloadClasses['WikiSyncJSONresult'] =
  166. $wgAutoloadClasses['WikiSyncClient'] = self::$ExtDir . '/WikiSyncClient.php';
  167. $wgAutoloadClasses['WikiSyncPage'] = self::$ExtDir . '/WikiSyncPage.php';
  168. $wgAutoloadClasses['WikiSyncExporter'] =
  169. $wgAutoloadClasses['WikiSyncImportReporter'] = self::$ExtDir . '/WikiSyncExporter.php';
  170. $wgAutoloadClasses['ApiWikiSync'] =
  171. $wgAutoloadClasses['ApiRevisionHistory'] =
  172. $wgAutoloadClasses['ApiFindSimilarRev'] =
  173. $wgAutoloadClasses['ApiGetFile'] =
  174. $wgAutoloadClasses['ApiSyncImport'] = self::$ExtDir . '/WikiSyncApi.php';
  175. $wgAPIModules['revisionhistory'] = 'ApiRevisionHistory';
  176. $wgAPIModules['similarrev'] = 'ApiFindSimilarRev';
  177. $wgAPIModules['getfile'] = 'ApiGetFile';
  178. $wgAPIModules['syncimport'] = 'ApiSyncImport';
  179. $wgAjaxExportList[] = 'WikiSyncClient::remoteLogin';
  180. $wgAjaxExportList[] = 'WikiSyncClient::localAPIget';
  181. $wgAjaxExportList[] = 'WikiSyncClient::remoteAPIget';
  182. $wgAjaxExportList[] = 'WikiSyncClient::syncXMLchunk';
  183. $wgAjaxExportList[] = 'WikiSyncClient::findNewFiles';
  184. $wgAjaxExportList[] = 'WikiSyncClient::transferFileBlock';
  185. $wgAjaxExportList[] = 'WikiSyncClient::uploadLocalFile';
  186. $wgHooks['ResourceLoaderRegisterModules'][] = 'WikiSyncSetup::registerModules';
  187. if ( ($parsed_url = parse_url( self::$proxy_address )) !== false ) {
  188. if ( isset( $parsed_url['host'] ) ) { self::$proxy_host = $parsed_url['host']; }
  189. if ( isset( $parsed_url['port'] ) ) { self::$proxy_port = $parsed_url['port']; }
  190. if ( isset( $parsed_url['user'] ) ) { self::$proxy_user = $parsed_url['user']; }
  191. if ( isset( $parsed_url['pass'] ) ) { self::$proxy_pass = $parsed_url['pass']; }
  192. }
  193. }
  194. static function setJSprefix( $val ) {
  195. return self::JS_MSG_PREFIX . $val;
  196. }
  197. /**
  198. * MW 1.17+ ResourceLoader module hook (JS,CSS)
  199. */
  200. static function registerModules( $resourceLoader ) {
  201. global $wgExtensionAssetsPath;
  202. $localpath = dirname( __FILE__ );
  203. $remotepath = "$wgExtensionAssetsPath/WikiSync";
  204. $resourceLoader->register(
  205. array(
  206. 'ext.wikisync' => new ResourceLoaderFileModule(
  207. array(
  208. 'scripts' => array( 'md5.js', 'WikiSync_utils.js', 'WikiSync.js' ),
  209. 'styles' => 'WikiSync.css',
  210. 'messages' => array_map( array( 'self', 'setJSprefix' ), self::$jsMessages )
  211. ),
  212. $localpath,
  213. $remotepath
  214. )
  215. )
  216. );
  217. return true;
  218. }
  219. /**
  220. * include stylesheets and scripts; set javascript variables
  221. * @param $outputPage - an instance of OutputPage
  222. * @param $isRTL - whether the current language is RTL
  223. */
  224. static function headScripts( &$outputPage, $isRTL ) {
  225. global $wgJsMimeType;
  226. if ( class_exists( 'ResourceLoader' ) ) {
  227. # $outputPage->addModules( 'jquery' );
  228. $outputPage->addModules( 'ext.wikisync' );
  229. return;
  230. }
  231. $outputPage->addLink(
  232. array( 'rel' => 'stylesheet', 'type' => 'text/css', 'href' => self::$ScriptPath . '/WikiSync.css?' . self::$version )
  233. );
  234. if ( $isRTL ) {
  235. $outputPage->addLink(
  236. array( 'rel' => 'stylesheet', 'type' => 'text/css', 'href' => self::$ScriptPath . '/WikiSync_rtl.css?' . self::$version )
  237. );
  238. }
  239. $outputPage->addScript(
  240. '<script type="' . $wgJsMimeType . '" src="' . self::$ScriptPath . '/md5.js?' . self::$version . '"></script>
  241. <script type="' . $wgJsMimeType . '" src="' . self::$ScriptPath . '/WikiSync_Utils.js?' . self::$version . '"></script>
  242. <script type="' . $wgJsMimeType . '" src="' . self::$ScriptPath . '/WikiSync.js?' . self::$version . '"></script>
  243. <script type="' . $wgJsMimeType . '">
  244. WikiSync.setLocalMessages(' . self::getJsObject( 'wsLocalMessages', self::$jsMessages ) . ');
  245. </script>
  246. '
  247. );
  248. }
  249. static function getJsObject( $method_name, $jsMessages ) {
  250. $result = array();
  251. foreach ( $jsMessages as $arg ) {
  252. $arg = self::JS_MSG_PREFIX . $arg;
  253. $result[$arg] = wfMsg( $arg );
  254. }
  255. return FormatJson::encode( $result );
  256. }
  257. static function checkUserMembership( $groups ) {
  258. global $wgLang;
  259. $ug = self::$user->getEffectiveGroups();
  260. if ( !self::$user->isAnon() && !in_array( 'user', $ug ) ) {
  261. $ug[] = 'user';
  262. }
  263. if ( array_intersect( $groups, $ug ) ) {
  264. return true;
  265. }
  266. return wfMsgExt( 'wikisync_api_result_noaccess', array( 'parsemag' ), $wgLang->commaList( $groups ), count( $groups ) );
  267. }
  268. /**
  269. * should not be called from LocalSettings.php
  270. * should be called only when the wiki is fully initialized
  271. * @param $direction defines the direction of synchronization
  272. * true - from remote to local wiki
  273. * false - from local to remote wiki
  274. * null - direction is undefined yet (any direction)
  275. * @return true, when the current user has access to synchronization;
  276. * string error message, when the current user has no access
  277. */
  278. static function initUser( $direction = null ) {
  279. global $wgUser, $wgRequest;
  280. self::$user = is_object( $wgUser ) ? $wgUser : new User();
  281. self::$response = $wgRequest->response();
  282. self::$cookie_prefix = 'WikiSync_' . md5( self::$user->getName() ) . '_';
  283. if ( self::$user->getName() !== self::WIKISYNC_BOT_NAME ) {
  284. return wfMsg( 'wikisync_unsupported_user', self::WIKISYNC_BOT_NAME );
  285. }
  286. if ( $direction === true ) {
  287. return self::checkUserMembership( self::$rtl_access_groups );
  288. } elseif ( $direction === false ) {
  289. return self::checkUserMembership( self::$ltr_access_groups );
  290. } elseif ( $direction === null ) {
  291. $groups = array_merge( self::$rtl_access_groups, self::$ltr_access_groups );
  292. return self::checkUserMembership( $groups );
  293. }
  294. return 'Bug: direction should be boolean or null, value (' . $direction . ') given in ' . __METHOD__;
  295. }
  296. static function getFullCookieName( $cookievar ) {
  297. global $wgCookiePrefix;
  298. if ( !is_string( self::$cookie_prefix ) ) {
  299. throw new MWException( 'You have to call CB_Setup::initUser before to use ' . __METHOD__ );
  300. }
  301. return $wgCookiePrefix . self::$cookie_prefix . $cookievar;
  302. }
  303. static function getJsCookiePrefix() {
  304. global $wgCookiePrefix;
  305. if ( !is_string( self::$cookie_prefix ) ) {
  306. throw new MWException( 'You have to call CB_Setup::initUser before to use ' . __METHOD__ );
  307. }
  308. return Xml::escapeJsString( $wgCookiePrefix . self::$cookie_prefix );
  309. }
  310. /**
  311. * set a cookie which is accessible in javascript
  312. */
  313. static function setCookie( $cookievar, $val, $time ) {
  314. global $wgCookieHttpOnly;
  315. // User::setCookies() is not suitable for our needs because it's called only for non-anonymous users
  316. // our cookie has to be accessible in javascript
  317. // todo: cookie is not set / read in JS anymore, don't modify $wgCookieHttpOnly
  318. $wgCookieHttpOnly_save = $wgCookieHttpOnly;
  319. $wgCookieHttpOnly = false;
  320. if ( !is_string( self::$cookie_prefix ) || !is_object( self::$response ) ) {
  321. throw new MWException( 'You have to call CB_Setup::initUser before to use ' . __METHOD__ );
  322. }
  323. self::$response->setcookie( self::$cookie_prefix . $cookievar, $val, $time );
  324. $wgCookieHttpOnly = $wgCookieHttpOnly_save;
  325. }
  326. static function getCookie( $cookievar ) {
  327. $idx = self::getFullCookieName( $cookievar );
  328. return isset( $_COOKIE[ $idx ] ) ? $_COOKIE[ $idx ] : null;
  329. }
  330. } /* end of WikiSyncSetup class */