Index: trunk/extensions/WikiSync/WikiSyncExporter.php |
— | — | @@ -28,7 +28,7 @@ |
29 | 29 | * * Add this line at the end of your LocalSettings.php file : |
30 | 30 | * require_once "$IP/extensions/WikiSync/WikiSync.php"; |
31 | 31 | * |
32 | | - * @version 0.2.1 |
| 32 | + * @version 0.3.1 |
33 | 33 | * @link http://www.mediawiki.org/wiki/Extension:WikiSync |
34 | 34 | * @author Dmitriy Sintsov <questpc@rambler.ru> |
35 | 35 | * @addtogroup Extensions |
Index: trunk/extensions/WikiSync/WikiSyncApi.php |
— | — | @@ -28,7 +28,7 @@ |
29 | 29 | * * Add this line at the end of your LocalSettings.php file : |
30 | 30 | * require_once "$IP/extensions/WikiSync/WikiSync.php"; |
31 | 31 | * |
32 | | - * @version 0.2.1 |
| 32 | + * @version 0.3.1 |
33 | 33 | * @link http://www.mediawiki.org/wiki/Extension:WikiSync |
34 | 34 | * @author Dmitriy Sintsov <questpc@rambler.ru> |
35 | 35 | * @addtogroup Extensions |
Index: trunk/extensions/WikiSync/WikiSyncPage.php |
— | — | @@ -28,7 +28,7 @@ |
29 | 29 | * * Add this line at the end of your LocalSettings.php file : |
30 | 30 | * require_once "$IP/extensions/WikiSync/WikiSync.php"; |
31 | 31 | * |
32 | | - * @version 0.2.1 |
| 32 | + * @version 0.3.1 |
33 | 33 | * @link http://www.mediawiki.org/wiki/Extension:WikiSync |
34 | 34 | * @author Dmitriy Sintsov <questpc@rambler.ru> |
35 | 35 | * @addtogroup Extensions |
— | — | @@ -40,9 +40,6 @@ |
41 | 41 | |
42 | 42 | class WikiSyncPage extends SpecialPage { |
43 | 43 | |
44 | | - var $sync_direction_tpl; |
45 | | - var $remote_login_form_tpl; |
46 | | - var $remote_log_tpl; |
47 | 44 | var $page_tpl; |
48 | 45 | |
49 | 46 | var $initUser; |
— | — | @@ -51,9 +48,9 @@ |
52 | 49 | $remote_wiki_root = _QXML::specialchars( WikiSyncSetup::$remote_wiki_root ); |
53 | 50 | $remote_wiki_user = _QXML::specialchars( WikiSyncSetup::$remote_wiki_user ); |
54 | 51 | $js_remote_change = 'return WikiSync.remoteRootChange(this)'; |
55 | | - $js_sync_files = 'return WikiSync.setSyncFiles(this);'; |
56 | | - $this->remote_login_form_tpl = |
57 | | - array( '__tag'=>'table', 'class'=>'wikisync_remote_login', |
| 52 | + $js_blur = 'return WikiSync.blurElement(this);'; |
| 53 | + return |
| 54 | + array( '__tag'=>'table', 'style'=>'width:100%; ', |
58 | 55 | array( '__tag'=>'form', 'id'=>'remote_login_form', 'onsubmit'=>'return WikiSync.submitRemoteLogin(this);', |
59 | 56 | array( '__tag'=>'tr', |
60 | 57 | array( '__tag'=>'th', 'colspan'=>'2', 'style'=>'text-align:center; ', wfMsgHtml( 'wikisync_login_to_remote_wiki' ) ) |
— | — | @@ -71,7 +68,13 @@ |
72 | 69 | array( '__tag'=>'td', array( '__tag'=>'input', 'type'=>'password', 'name'=>'remote_wiki_pass' ) ) |
73 | 70 | ), |
74 | 71 | array( '__tag'=>'tr', |
75 | | - 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'=>'' ) ) |
| 72 | + array( '__tag'=>'td', 'colspan'=>'2', |
| 73 | + wfMsgHtml( 'wikisync_sync_files' ), |
| 74 | + array( '__tag'=>'input', 'type'=>'checkbox', 'id'=>'ws_sync_files', 'name'=>'ws_sync_files', 'onchange'=>$js_blur, 'onmouseup'=>$js_blur, 'checked'=>'' ), |
| 75 | + array( '__tag'=>'br', 'clear'=>'all', '' ), |
| 76 | + wfMsgHtml( 'wikisync_store_password' ), |
| 77 | + array( '__tag'=>'input', 'type'=>'checkbox', 'id'=>'ws_store_password', 'name'=>'ws_store_password', 'onchange'=>$js_blur, 'onmouseup'=>$js_blur ) |
| 78 | + ) |
76 | 79 | ), |
77 | 80 | array( '__tag'=>'tr', |
78 | 81 | array( '__tag'=>'td', array( '__tag'=>'input', 'id'=>'wikisync_synchronization_button', 'type'=>'button', 'value'=>wfMsgHtml( 'wikisync_synchronization_button' ), 'disabled'=>'', 'onclick'=>'return WikiSync.process(\'init\')' ) ), |
— | — | @@ -81,20 +84,48 @@ |
82 | 85 | ); |
83 | 86 | } |
84 | 87 | |
85 | | - function initRemoteLogTpl() { |
86 | | - $this->remote_log_tpl = |
87 | | - array( '__tag'=>'table', 'class'=>'wikisync_remote_log', |
| 88 | + function initSchedulerTpl() { |
| 89 | + $js_blur = 'return WikiSync.blurElement(this);'; |
| 90 | + return |
| 91 | + array( '__tag'=>'table', 'style'=>'width:100%; ', |
| 92 | + array( '__tag'=>'form', 'id'=>'scheduler_form', 'onsubmit'=>'return WikiSyncScheduler.setup(this);', |
| 93 | + array( '__tag'=>'tr', |
| 94 | + array( '__tag'=>'th', 'colspan'=>'2', 'style'=>'text-align:center; ', wfMsgHtml( 'wikisync_scheduler_setup' ) ) |
| 95 | + ), |
| 96 | + array( '__tag'=>'tr', |
| 97 | + array( '__tag'=>'td', 'colspan'=>'2', |
| 98 | + wfMsgHtml( 'wikisync_scheduler_turn_on' ), |
| 99 | + array( '__tag'=>'input', 'type'=>'checkbox', 'name'=>'ws_auto_sync', 'onchange'=>$js_blur, 'onmouseup'=>$js_blur ), |
| 100 | + array( '__tag'=>'br', 'clear'=>'all', '' ), |
| 101 | + wfMsgHtml( 'wikisync_scheduler_switch_direction' ), |
| 102 | + array( '__tag'=>'input', 'type'=>'checkbox', 'name'=>'ws_auto_switch_direction', 'onchange'=>$js_blur, 'onmouseup'=>$js_blur ), |
| 103 | + array( '__tag'=>'br', 'clear'=>'all', '' ), |
| 104 | + wfMsgHtml( 'wikisync_scheduler_time_interval' ), |
| 105 | + array( '__tag'=>'input', 'type'=>'text', 'style'=>'margin-left:3px; width:3em; ', 'name'=>'ws_auto_sync_time_interval' ) |
| 106 | + ) |
| 107 | + ), |
| 108 | + array( '__tag'=>'tr', |
| 109 | + array( '__tag'=>'td', 'id'=>'ws_scheduler_countdown', '' ), // a placeholder for scheduled time countdown in javascript |
| 110 | + array( '__tag'=>'td', 'style'=>'text-align:right; ', array( '__tag'=>'input', 'id'=>'wikisync_scheduler_apply_button', 'type'=>'submit', 'value'=>wfMsgHtml( 'wikisync_apply_button' ) ) ) |
| 111 | + ) |
| 112 | + ) |
| 113 | + ); |
| 114 | + } |
| 115 | + |
| 116 | + function initLogTpl( $log_id ) { |
| 117 | + return |
| 118 | + array( '__tag'=>'table', 'style'=>'width:100%; ', |
88 | 119 | array( '__tag'=>'tr', |
89 | | - array( '__tag'=>'th', 'style'=>'text-align:center; ', wfMsgHtml( 'wikisync_remote_log' ) ) |
| 120 | + array( '__tag'=>'th', 'style'=>'text-align:center; ', wfMsgHtml( $log_id ) ) |
90 | 121 | ), |
91 | 122 | array( '__tag'=>'tr', |
92 | 123 | array( '__tag'=>'td', |
93 | | - array( '__tag'=>'div', 'id'=>'wikisync_remote_log' ) |
| 124 | + array( '__tag'=>'div', 'class'=>'wikisync_log', 'id'=>$log_id ) |
94 | 125 | ) |
95 | 126 | ), |
96 | 127 | array( '__tag'=>'tr', |
97 | 128 | array( '__tag'=>'td', |
98 | | - array( '__tag'=>'input', 'type'=>'button', 'value'=>wfMsgHtml( 'wikisync_clear_log' ), 'onclick'=>'return WikiSync.clearLog()' ) |
| 129 | + array( '__tag'=>'input', 'type'=>'button', 'value'=>wfMsgHtml( 'wikisync_clear_log' ), 'onclick'=>'return WikiSync.clearLog(\'' . $log_id . '\')' ) |
99 | 130 | ) |
100 | 131 | ) |
101 | 132 | ); |
— | — | @@ -102,13 +133,13 @@ |
103 | 134 | |
104 | 135 | function initSyncDirectionTpl() { |
105 | 136 | global $wgServer, $wgScriptPath; |
106 | | - $this->sync_direction_tpl = |
| 137 | + return |
107 | 138 | array( |
108 | 139 | array( '__tag'=>'div', 'style'=>'width:100%; font-weight:bold; text-align:center; ', wfMsgHTML( 'wikisync_direction' ) ), |
109 | 140 | array( '__tag'=>'table', 'style'=>'margin:0 auto 0 auto; ', |
110 | 141 | array( '__tag'=>'tr', |
111 | 142 | array( '__tag'=>'td', 'style'=>'text-align:right; ', wfMsgHTML( 'wikisync_local_root' ) ), |
112 | | - array( '__tag'=>'td', 'rowspan'=>'2', 'style'=>'vertical-align:middle; ', array( '__tag'=>'input', 'id'=>'wikisync_direction_button', 'type'=>'button', 'value'=>'<=', 'onclick'=>'return WikiSync.setDirection(this)' ) ), |
| 143 | + array( '__tag'=>'td', 'rowspan'=>'2', 'style'=>'vertical-align:middle; ', array( '__tag'=>'input', 'id'=>'wikisync_direction_button', 'type'=>'button', 'value'=>'<=', 'onclick'=>'return WikiSync.switchDirection(this)' ) ), |
113 | 144 | array( '__tag'=>'td', wfMsgHTML( 'wikisync_remote_root' ) ) |
114 | 145 | ), |
115 | 146 | array( '__tag'=>'tr', |
— | — | @@ -134,15 +165,28 @@ |
135 | 166 | } |
136 | 167 | |
137 | 168 | function initPageTpl() { |
| 169 | + $tr_style = 'border:2px dashed lightgray; '; |
138 | 170 | $this->page_tpl = |
139 | 171 | array( '__tag'=>'table', |
140 | | - array( '__tag'=>'tr', |
141 | | - array( '__tag'=>'td', 'colspan'=>'2', &$this->sync_direction_tpl ) |
| 172 | + array( '__tag'=>'tr', 'style'=>$tr_style, |
| 173 | + array( '__tag'=>'td', 'colspan'=>'2', $this->initSyncDirectionTpl() ) |
142 | 174 | ), |
143 | | - array( '__tag'=>'tr', |
144 | | - array( '__tag'=>'td', 'style'=>'width:50%; ', &$this->remote_log_tpl ), |
145 | | - array( '__tag'=>'td', 'style'=>'width:50%; ', &$this->remote_login_form_tpl ) |
| 175 | + array( '__tag'=>'tr', 'style'=>$tr_style, |
| 176 | + array( '__tag'=>'td', 'style'=>'width:50%; ', |
| 177 | + $this->initLogTpl( 'wikisync_remote_log' ), |
| 178 | + ), |
| 179 | + array( '__tag'=>'td', 'style'=>'width:50%; ', |
| 180 | + $this->initRemoteLoginFormTpl(), |
| 181 | + ) |
146 | 182 | ), |
| 183 | + array( '__tag'=>'tr', 'style'=>$tr_style, |
| 184 | + array( '__tag'=>'td', 'style'=>'width:50%; ', |
| 185 | + $this->initLogTpl( 'wikisync_scheduler_log' ), |
| 186 | + ), |
| 187 | + array( '__tag'=>'td', 'style'=>'width:50%; ', |
| 188 | + $this->initSchedulerTpl() |
| 189 | + ) |
| 190 | + ), |
147 | 191 | array( '__tag'=>'tr', |
148 | 192 | array( '__tag'=>'td', 'colspan'=>'2', |
149 | 193 | $this->initPercentsIndicatorTpl( 'wikisync_xml_percents' ), |
— | — | @@ -156,6 +200,7 @@ |
157 | 201 | array( '__tag'=>'td', 'colspan'=>'2', |
158 | 202 | // Have to explicitly set empty contents for the iframe, or we'll produce |
159 | 203 | // <iframe /> which browsers consider an unclosed tag |
| 204 | + // todo: fix in _QXML class |
160 | 205 | array( '__tag'=> 'iframe', 'id'=>'wikisync_iframe', 'style' => 'width:100%; height:200px; display:none; ', '' ) |
161 | 206 | ) |
162 | 207 | ) |
— | — | @@ -186,9 +231,6 @@ |
187 | 232 | } |
188 | 233 | WikiSyncSetup::headScripts( $wgOut, $wgContLang->isRTL() ); |
189 | 234 | $wgOut->setPagetitle( wfMsgHtml( 'wikisync' ) ); |
190 | | - $this->initSyncDirectionTpl(); |
191 | | - $this->initRemoteLoginFormTpl(); |
192 | | - $this->initRemoteLogTpl(); |
193 | 235 | $this->initPageTpl(); |
194 | 236 | $wgOut->addHTML( _QXML::toText( $this->page_tpl ) ); |
195 | 237 | } |
Index: trunk/extensions/WikiSync/WikiSyncClient.php |
— | — | @@ -28,7 +28,7 @@ |
29 | 29 | * * Add this line at the end of your LocalSettings.php file : |
30 | 30 | * require_once "$IP/extensions/WikiSync/WikiSync.php"; |
31 | 31 | * |
32 | | - * @version 0.2.1 |
| 32 | + * @version 0.3.1 |
33 | 33 | * @link http://www.mediawiki.org/wiki/Extension:WikiSync |
34 | 34 | * @author Dmitriy Sintsov <questpc@rambler.ru> |
35 | 35 | * @addtogroup Extensions |
— | — | @@ -265,6 +265,8 @@ |
266 | 266 | * @param $args[0] : remote wiki root |
267 | 267 | * @param $args[1] : remote wiki user |
268 | 268 | * @param $args[2] : remote wiki password |
| 269 | + * @param $args[3] : string "boolean", whether the login / password should be stored |
| 270 | + * in cookies; "true" - yes, "false" - not |
269 | 271 | * @return JSON result of second phase login (token confirmed, used logged in) from the remote API |
270 | 272 | */ |
271 | 273 | static function remoteLogin() { |
— | — | @@ -279,6 +281,12 @@ |
280 | 282 | # not enough priviledges to run this method |
281 | 283 | return $json_result->getResult( 'noaccess', $iu ); |
282 | 284 | } |
| 285 | + $store_rlogin = count( $args ) > 3 && $args[3] === 'true'; |
| 286 | + if ( !$store_rlogin ) { |
| 287 | + // unset cookies, if there were any |
| 288 | + WikiSyncSetup::setCookie( 'ruser', '', 0 ); |
| 289 | + WikiSyncSetup::setCookie( 'rpass', '', 0 ); |
| 290 | + } |
283 | 291 | $snoopy = new WikiSnoopy(); |
284 | 292 | list( $remote_wiki_root, $remote_wiki_user, $remote_wiki_password ) = $args; |
285 | 293 | $snoopy->setContext( array( 'wikiroot'=>$remote_wiki_root ) ); |
— | — | @@ -324,6 +332,10 @@ |
325 | 333 | } |
326 | 334 | if ( $response->login->result === 'Success' ) { |
327 | 335 | $json_result->setStatus( '1' ); // success |
| 336 | + if ( $store_rlogin ) { |
| 337 | + WikiSyncSetup::setCookie( 'ruser', $remote_wiki_user, time() + WikiSyncSetup::COOKIE_EXPIRE_TIME ); |
| 338 | + WikiSyncSetup::setCookie( 'rpass', $remote_wiki_password, time() + WikiSyncSetup::COOKIE_EXPIRE_TIME ); |
| 339 | + } |
328 | 340 | $r = array( |
329 | 341 | 'userid' => $response->login->lguserid, |
330 | 342 | 'username' => $response->login->lgusername, // may return a different one ? |
Index: trunk/extensions/WikiSync/WikiSync.php |
— | — | @@ -28,7 +28,7 @@ |
29 | 29 | * * Add this line at the end of your LocalSettings.php file : |
30 | 30 | * require_once "$IP/extensions/WikiSync/WikiSync.php"; |
31 | 31 | * |
32 | | - * @version 0.2.1 |
| 32 | + * @version 0.3.1 |
33 | 33 | * @link http://www.mediawiki.org/wiki/Extension:WikiSync |
34 | 34 | * @author Dmitriy Sintsov <questpc@rambler.ru> |
35 | 35 | * @addtogroup Extensions |
— | — | @@ -73,6 +73,9 @@ |
74 | 74 | } |
75 | 75 | |
76 | 76 | class WikiSyncSetup { |
| 77 | + |
| 78 | + const COOKIE_EXPIRE_TIME = 2592000; // 60 * 60 * 24 * 30; see also WikiSync.js, WikiSync.cookieExpireTime |
| 79 | + |
77 | 80 | # {{{ changable in LocalSettings.php : |
78 | 81 | static $remote_wiki_root = 'http://www.mediawiki.org/w'; |
79 | 82 | static $remote_wiki_user = ''; |
— | — | @@ -90,10 +93,14 @@ |
91 | 94 | static $proxy_pass = ''; |
92 | 95 | # }}} |
93 | 96 | |
94 | | - static $version = '0.2.1'; // version of extension |
| 97 | + static $version = '0.3.1'; // version of extension |
95 | 98 | static $ExtDir; // filesys path with windows path fix |
96 | 99 | static $ScriptPath; // apache virtual path |
97 | 100 | |
| 101 | + static $user; // current User |
| 102 | + static $cookie_prefix; // an extension's cookie prefix for current User |
| 103 | + static $response; // an extension's WebResponse |
| 104 | + |
98 | 105 | const JS_MSG_PREFIX = 'wikisync_js_'; |
99 | 106 | static $jsMessages = array( |
100 | 107 | 'last_op_error', |
— | — | @@ -103,7 +110,13 @@ |
104 | 111 | 'sync_to_itself', |
105 | 112 | 'diff_search', |
106 | 113 | 'revision', |
107 | | - 'file_size_mismatch' |
| 114 | + 'file_size_mismatch', |
| 115 | + 'invalid_scheduler_time', |
| 116 | + 'scheduler_countdown', |
| 117 | + 'sync_start_ltr', |
| 118 | + 'sync_start_rtl', |
| 119 | + 'sync_end_ltr', |
| 120 | + 'sync_end_rtl' |
108 | 121 | ); |
109 | 122 | |
110 | 123 | static function init() { |
— | — | @@ -172,7 +185,7 @@ |
173 | 186 | array( |
174 | 187 | 'ext.wikisync' => new ResourceLoaderFileModule( |
175 | 188 | array( |
176 | | - 'scripts' => array( 'WikiSync_utils.js', 'WikiSync.js' ), |
| 189 | + 'scripts' => array( 'md5.js', 'WikiSync_utils.js', 'WikiSync.js' ), |
177 | 190 | 'styles' => 'WikiSync.css', |
178 | 191 | 'messages' => array_map( 'self::setJSprefix', self::$jsMessages ) |
179 | 192 | ), |
— | — | @@ -182,9 +195,9 @@ |
183 | 196 | ) |
184 | 197 | ); |
185 | 198 | return true; |
186 | | - } |
| 199 | + } |
187 | 200 | |
188 | | - /* |
| 201 | + /** |
189 | 202 | * include stylesheets and scripts; set javascript variables |
190 | 203 | * @param $outputPage - an instance of OutputPage |
191 | 204 | * @param $isRTL - whether the current language is RTL |
— | — | @@ -205,12 +218,13 @@ |
206 | 219 | ); |
207 | 220 | } |
208 | 221 | $outputPage->addScript( |
209 | | - '<script type="' . $wgJsMimeType . '" src="' . self::$ScriptPath . '/WikiSync_Utils.js?' . self::$version . '"></script> |
| 222 | + '<script type="' . $wgJsMimeType . '" src="' . self::$ScriptPath . '/md5.js?' . self::$version . '"></script> |
| 223 | + <script type="' . $wgJsMimeType . '" src="' . self::$ScriptPath . '/WikiSync_Utils.js?' . self::$version . '"></script> |
210 | 224 | <script type="' . $wgJsMimeType . '" src="' . self::$ScriptPath . '/WikiSync.js?' . self::$version . '"></script> |
211 | 225 | <script type="' . $wgJsMimeType . '"> |
212 | | - WikiSync.setLocalMessages( ' . |
213 | | - self::getJsObject( 'wsLocalMessages', self::$jsMessages ) . |
214 | | - ');</script>' . "\n" |
| 226 | + WikiSync.setLocalMessages(' . self::getJsObject( 'wsLocalMessages', self::$jsMessages ) . '); |
| 227 | + </script> |
| 228 | +' |
215 | 229 | ); |
216 | 230 | } |
217 | 231 | |
— | — | @@ -235,7 +249,7 @@ |
236 | 250 | return wfMsg( 'wikisync_api_result_noaccess', $wgLang->commaList( $groups ) ); |
237 | 251 | } |
238 | 252 | |
239 | | - /* |
| 253 | + /** |
240 | 254 | * should not be called from LocalSettings.php |
241 | 255 | * should be called only when the wiki is fully initialized |
242 | 256 | * @param $direction defines the direction of synchronization |
— | — | @@ -246,7 +260,11 @@ |
247 | 261 | * string error message, when the current user has no access |
248 | 262 | */ |
249 | 263 | static function initUser( $direction = null) { |
| 264 | + global $wgUser, $wgRequest; |
| 265 | + self::$user = is_object( $wgUser ) ? $wgUser : new User(); |
| 266 | + self::$response = $wgRequest->response(); |
250 | 267 | wfLoadExtensionMessages( 'WikiSync' ); |
| 268 | + self::$cookie_prefix = 'WikiSync_' . md5( self::$user->getName() ) . '_'; |
251 | 269 | if ( $direction === true ) { |
252 | 270 | return self::checkUserMembership( self::$rtl_access_groups ); |
253 | 271 | } elseif ( $direction === false ) { |
— | — | @@ -258,4 +276,42 @@ |
259 | 277 | return 'Bug: direction should be boolean or null, value (' . $direction . ') given in ' . __METHOD__; |
260 | 278 | } |
261 | 279 | |
| 280 | + static function getFullCookieName( $cookievar ) { |
| 281 | + global $wgCookiePrefix; |
| 282 | + if ( !is_string( self::$cookie_prefix ) ) { |
| 283 | + throw new MWException( 'You have to call CB_Setup::initUser before to use ' . __METHOD__ ); |
| 284 | + } |
| 285 | + return $wgCookiePrefix . self::$cookie_prefix . $cookievar; |
| 286 | + } |
| 287 | + |
| 288 | + static function getJsCookiePrefix() { |
| 289 | + global $wgCookiePrefix; |
| 290 | + if ( !is_string( self::$cookie_prefix ) ) { |
| 291 | + throw new MWException( 'You have to call CB_Setup::initUser before to use ' . __METHOD__ ); |
| 292 | + } |
| 293 | + return Xml::escapeJsString( $wgCookiePrefix . self::$cookie_prefix ); |
| 294 | + } |
| 295 | + |
| 296 | + /** |
| 297 | + * set a cookie which is accessible in javascript |
| 298 | + */ |
| 299 | + static function setCookie( $cookievar, $val, $time ) { |
| 300 | + global $wgCookieHttpOnly; |
| 301 | + // User::setCookies() is not suitable for our needs because it's called only for non-anonymous users |
| 302 | + // our cookie has to be accessible in javascript |
| 303 | + // todo: cookie is not set / read in JS anymore, don't modify $wgCookieHttpOnly |
| 304 | + $wgCookieHttpOnly_save = $wgCookieHttpOnly; |
| 305 | + $wgCookieHttpOnly = false; |
| 306 | + if ( !is_string( self::$cookie_prefix ) || !is_object( self::$response ) ) { |
| 307 | + throw new MWException( 'You have to call CB_Setup::initUser before to use ' . __METHOD__ ); |
| 308 | + } |
| 309 | + self::$response->setcookie( self::$cookie_prefix . $cookievar, $val, $time ); |
| 310 | + $wgCookieHttpOnly = $wgCookieHttpOnly_save; |
| 311 | + } |
| 312 | + |
| 313 | + static function getCookie( $cookievar ) { |
| 314 | + $idx = self::getFullCookieName( $cookievar ); |
| 315 | + return isset( $_COOKIE[ $idx ] ) ? $_COOKIE[ $idx ] : null; |
| 316 | + } |
| 317 | + |
262 | 318 | } /* end of WikiSyncSetup class */ |
Index: trunk/extensions/WikiSync/WikiSync.css |
— | — | @@ -2,12 +2,12 @@ |
3 | 3 | width: 15em; |
4 | 4 | } |
5 | 5 | |
6 | | -div#wikisync_remote_log { |
| 6 | +div.wikisync_log { |
7 | 7 | color: darkblue; |
8 | 8 | background-color: lightgray; |
9 | 9 | border: 1px solid gray; |
10 | 10 | width: 30em; |
11 | | - height: 12em; |
| 11 | + height: 9em; |
12 | 12 | overflow: auto; |
13 | 13 | padding: 5px; |
14 | 14 | } |
Index: trunk/extensions/WikiSync/WikiSyncBasic.php |
— | — | @@ -28,7 +28,7 @@ |
29 | 29 | * * Add this line at the end of your LocalSettings.php file : |
30 | 30 | * require_once "$IP/extensions/WikiSync/WikiSync.php"; |
31 | 31 | * |
32 | | - * @version 0.2.1 |
| 32 | + * @version 0.3.1 |
33 | 33 | * @link http://www.mediawiki.org/wiki/Extension:WikiSync |
34 | 34 | * @author Dmitriy Sintsov <questpc@rambler.ru> |
35 | 35 | * @addtogroup Extensions |
Index: trunk/extensions/WikiSync/WikiSync_utils.js |
— | — | @@ -1,4 +1,14 @@ |
2 | 2 | window.WikiSyncUtils = { |
| 3 | + |
| 4 | + mathLogBase : function( x, base ) { |
| 5 | + return Math.log( x ) / Math.log( base ); |
| 6 | + }, |
| 7 | + |
| 8 | + getLocalDate : function() { |
| 9 | + var today = new Date(); |
| 10 | + return today.toLocaleString(); |
| 11 | + }, |
| 12 | + |
3 | 13 | // browser-independent addevent function |
4 | 14 | addEvent : function ( obj, type, fn ) { |
5 | 15 | if ( document.getElementById && document.createTextNode ) { |
— | — | @@ -30,6 +40,88 @@ |
31 | 41 | } |
32 | 42 | } |
33 | 43 | return obj; |
| 44 | + }, |
| 45 | + |
| 46 | + /** |
| 47 | + * taken from http://phpjs.org/functions/urldecode:572 |
| 48 | + */ |
| 49 | + urldecode : function( s ) { |
| 50 | + return decodeURIComponent( s.replace(/\+/g, '%20') ); |
| 51 | + }, |
| 52 | + |
| 53 | + // basename prefix of user's cookies |
| 54 | + cookiePrefix : null, |
| 55 | + |
| 56 | + /* |
| 57 | + */ |
| 58 | + setCookiePrefix : function( name ) { |
| 59 | + this.cookiePrefix = name; |
| 60 | + }, |
| 61 | + |
| 62 | + /* |
| 63 | + * @return empty string in case cookie value is empty, null when cookie is not set |
| 64 | + */ |
| 65 | + getCookie : function ( cookieName ) { |
| 66 | + var ca, cn, keyval, key, val; |
| 67 | + ca = document.cookie.split( ';' ); |
| 68 | + for ( var i=0; i < ca.length; i++ ) { |
| 69 | + keyval = ca[i].split( '=' ); |
| 70 | + // trim whitespace |
| 71 | + key = keyval[0].replace(/^\s+|\s+$/g, ''); |
| 72 | + if ( key == (this.cookiePrefix + cookieName) ) { |
| 73 | + if ( keyval.length > 1 ) { |
| 74 | + return this.urldecode( keyval[1].replace(/^\s+|\s+$/g, '') ); |
| 75 | + } else { |
| 76 | + // cookie exists but has no value |
| 77 | + return ""; |
| 78 | + } |
| 79 | + } |
| 80 | + } |
| 81 | + // cookie not found |
| 82 | + return null; |
| 83 | + }, |
| 84 | + |
| 85 | + /* |
| 86 | + * usage example: WikiSyncUtils.setCookie( 'rootcond', eventObj.value, 24 * 60 * 60, '/' ); |
| 87 | + */ |
| 88 | + setCookie : function( cookieName, value, expires, path, domain, secure ) { |
| 89 | + // set time, it's in milliseconds |
| 90 | + var today = new Date(); |
| 91 | + today.setTime( today.getTime() ); |
| 92 | + |
| 93 | + /* |
| 94 | + if the expires variable is set, make the correct |
| 95 | + expires time, the current script below will set |
| 96 | + it for x number of days, to make it for hours, |
| 97 | + delete * 24, for minutes, delete * 60 * 24 |
| 98 | + */ |
| 99 | + if ( expires ) { |
| 100 | + expires = expires * 1000 // * 60 * 60 * 24; |
| 101 | + } |
| 102 | + var expires_date = new Date( today.getTime() + expires ); |
| 103 | + |
| 104 | + document.cookie = this.cookiePrefix + cookieName + "=" +escape( value ) + |
| 105 | + ( ( expires ) ? ";expires=" + expires_date.toGMTString() : "" ) + |
| 106 | + ( ( path ) ? ";path=" + path : "" ) + |
| 107 | + ( ( domain ) ? ";domain=" + domain : "" ) + |
| 108 | + ( ( secure ) ? ";secure" : "" ); |
| 109 | + }, |
| 110 | + |
| 111 | + cookieToInput : function( cookie, input ) { |
| 112 | + var val = this.getCookie( cookie ); |
| 113 | + if ( val !== null ) { |
| 114 | + input.value = val; |
| 115 | + } |
| 116 | + return val; |
| 117 | + }, |
| 118 | + |
| 119 | + cookieToCheckbox : function( cookie, input ) { |
| 120 | + var val = this.getCookie( cookie ); |
| 121 | + if ( val !== null ) { |
| 122 | + val = val === 'true'; |
| 123 | + input.checked = val; |
| 124 | + } |
| 125 | + return val; |
34 | 126 | } |
35 | 127 | |
36 | 128 | }; |
— | — | @@ -49,9 +141,11 @@ |
50 | 142 | this.td2 = this.td1.nextSibling; |
51 | 143 | this.reset(); |
52 | 144 | } |
| 145 | + |
53 | 146 | WikiSyncPercentsIndicator.prototype.setVisibility = function( visible ) { |
54 | 147 | this.topElement.style.display = visible ? 'table' : 'none'; |
55 | 148 | } |
| 149 | + |
56 | 150 | /** |
57 | 151 | * @access private |
58 | 152 | */ |
— | — | @@ -59,10 +153,12 @@ |
60 | 154 | element.style.display = (percent > 0) ? 'table-cell' : 'none'; |
61 | 155 | element.style.width = percent + '%'; |
62 | 156 | } |
| 157 | + |
63 | 158 | WikiSyncPercentsIndicator.prototype.reset = function() { |
64 | 159 | this.iterations = { 'desc' : '', 'curr' : 0, 'min' : 0, 'max' : 0 }; |
65 | 160 | this.display(); |
66 | | -}, |
| 161 | +} |
| 162 | + |
67 | 163 | WikiSyncPercentsIndicator.prototype.display = function( indicator ) { |
68 | 164 | if ( typeof indicator !== 'undefined' ) { |
69 | 165 | if ( typeof indicator.desc !== 'undefined' ) { |
— | — | @@ -104,4 +200,41 @@ |
105 | 201 | // show percent |
106 | 202 | this.setPercents( this.td1, percent ); |
107 | 203 | this.setPercents( this.td2, 100 - percent ); |
108 | | -} /* end of WikiSyncPercentsIndicator class */ |
| 204 | +} |
| 205 | +/* end of WikiSyncPercentsIndicator class */ |
| 206 | + |
| 207 | +/** |
| 208 | + * Debug / output logger class |
| 209 | + * @param logId - id of DIV container for logger |
| 210 | + */ |
| 211 | +function WikiSyncLog( logId ) { |
| 212 | + this.logContainer = document.getElementById( logId ); |
| 213 | +} |
| 214 | + |
| 215 | +WikiSyncLog.prototype.log = function( s, color, type ) { |
| 216 | + var span = document.createElement( 'SPAN' ); |
| 217 | + if ( typeof s === 'object' ) { |
| 218 | + s = JSON.stringify( s ); |
| 219 | + } |
| 220 | + if ( typeof type !== 'undefined' ) { |
| 221 | + var b = document.createElement( 'B' ); |
| 222 | + b.appendChild( document.createTextNode( type + ': ' ) ); |
| 223 | + span.appendChild( b ); |
| 224 | + } |
| 225 | + span.appendChild( document.createTextNode( s ) ); |
| 226 | + if ( typeof color !== 'undefined' ) { |
| 227 | + span.style.color = color; |
| 228 | + } |
| 229 | + this.logContainer.appendChild( span ); |
| 230 | + this.logContainer.appendChild( document.createElement( 'HR' ) ); |
| 231 | + this.logContainer.scrollTop = this.logContainer.scrollHeight; |
| 232 | +} |
| 233 | + |
| 234 | +/** |
| 235 | + * note: may be used as "inline html" event handler, thus should return true / false |
| 236 | + */ |
| 237 | +WikiSyncLog.prototype.clear = function() { |
| 238 | + this.logContainer.innerHTML = ''; |
| 239 | + return false; |
| 240 | +} |
| 241 | +/* end of WikiSyncLog class */ |
\ No newline at end of file |
Index: trunk/extensions/WikiSync/README |
— | — | @@ -1,4 +1,4 @@ |
2 | | -MediaWiki extension WikiSync, version 0.2.1 |
| 2 | +MediaWiki extension WikiSync, version 0.3.1 |
3 | 3 | |
4 | 4 | WikiSync allows an AJAX-based synchronization of revisions and files between |
5 | 5 | global wiki site and it's local mirror. Files download can optionally be disabled, |
Index: trunk/extensions/WikiSync/md5.js |
— | — | @@ -0,0 +1,363 @@ |
| 2 | +/* |
| 3 | + * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message |
| 4 | + * Digest Algorithm, as defined in RFC 1321. |
| 5 | + * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009 |
| 6 | + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet |
| 7 | + * Distributed under the BSD License |
| 8 | + * See http://pajhome.org.uk/crypt/md5 for more info. |
| 9 | + */ |
| 10 | + |
| 11 | +// ported to use object notation |
| 12 | +// I've choosed WikiSync prefix to make window scope conflict unlikely to happen |
| 13 | +window.WikiSync_md5 = { |
| 14 | + |
| 15 | +/* |
| 16 | + * Configurable variables. You may need to tweak these to be compatible with |
| 17 | + * the server-side, but the defaults work in most cases. |
| 18 | + */ |
| 19 | +hexcase : 0, /* hex output format. 0 - lowercase; 1 - uppercase */ |
| 20 | +b64pad : "", /* base-64 pad character. "=" for strict RFC compliance */ |
| 21 | + |
| 22 | +/* |
| 23 | + * These are the functions you'll usually want to call |
| 24 | + * They take string arguments and return either hex or base-64 encoded strings |
| 25 | + */ |
| 26 | +hex : function (s) { return this.rstr2hex(this.rstr(this.str2rstr_utf8(s))); }, |
| 27 | +b64 : function (s) { return this.rstr2b64(this.rstr(this.str2rstr_utf8(s))); }, |
| 28 | +any : function (s, e) { return this.rstr2any(this.rstr(this.str2rstr_utf8(s)), e); }, |
| 29 | +hex_hmac : function (k, d) { return this.rstr2hex(this.rstr_hmac(this.str2rstr_utf8(k), this.str2rstr_utf8(d))); }, |
| 30 | +b64_hmac : function(k, d) { return this.rstr2b64(this.rstr_hmac(this.str2rstr_utf8(k), this.str2rstr_utf8(d))); }, |
| 31 | +any_hmac : function(k, d, e) { return this.rstr2any(this.rstr_hmac(this.str2rstr_utf8(k), this.str2rstr_utf8(d)), e); }, |
| 32 | + |
| 33 | +/* |
| 34 | + * Perform a simple self-test to see if the VM is working |
| 35 | + */ |
| 36 | +md5_vm_test : function () { |
| 37 | + return this.hex("abc").toLowerCase() == "900150983cd24fb0d6963f7d28e17f72"; |
| 38 | +}, |
| 39 | + |
| 40 | +/* |
| 41 | + * Calculate the MD5 of a raw string |
| 42 | + */ |
| 43 | +rstr : function(s) { |
| 44 | + return this.binl2rstr(this.binl(this.rstr2binl(s), s.length * 8)); |
| 45 | +}, |
| 46 | + |
| 47 | +/* |
| 48 | + * Calculate the HMAC-MD5, of a key and some data (raw strings) |
| 49 | + */ |
| 50 | +rstr_hmac : function(key, data) { |
| 51 | + var bkey = this.rstr2binl(key); |
| 52 | + if(bkey.length > 16) bkey = this.binl(bkey, key.length * 8); |
| 53 | + |
| 54 | + var ipad = Array(16), opad = Array(16); |
| 55 | + for(var i = 0; i < 16; i++) |
| 56 | + { |
| 57 | + ipad[i] = bkey[i] ^ 0x36363636; |
| 58 | + opad[i] = bkey[i] ^ 0x5C5C5C5C; |
| 59 | + } |
| 60 | + |
| 61 | + var hash = this.binl(ipad.concat(this.rstr2binl(data)), 512 + data.length * 8); |
| 62 | + return this.binl2rstr(this.binl(opad.concat(hash), 512 + 128)); |
| 63 | +}, |
| 64 | + |
| 65 | +/* |
| 66 | + * Convert a raw string to a hex string |
| 67 | + */ |
| 68 | +rstr2hex : function (input) { |
| 69 | + try { this.hexcase } catch(e) { this.hexcase=0; } |
| 70 | + var hex_tab = this.hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; |
| 71 | + var output = ""; |
| 72 | + var x; |
| 73 | + for(var i = 0; i < input.length; i++) |
| 74 | + { |
| 75 | + x = input.charCodeAt(i); |
| 76 | + output += hex_tab.charAt((x >>> 4) & 0x0F) |
| 77 | + + hex_tab.charAt( x & 0x0F); |
| 78 | + } |
| 79 | + return output; |
| 80 | +}, |
| 81 | + |
| 82 | +/* |
| 83 | + * Convert a raw string to a base-64 string |
| 84 | + */ |
| 85 | +rstr2b64 : function(input) { |
| 86 | + try { this.b64pad } catch(e) { this.b64pad=''; } |
| 87 | + var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
| 88 | + var output = ""; |
| 89 | + var len = input.length; |
| 90 | + for(var i = 0; i < len; i += 3) |
| 91 | + { |
| 92 | + var triplet = (input.charCodeAt(i) << 16) |
| 93 | + | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0) |
| 94 | + | (i + 2 < len ? input.charCodeAt(i+2) : 0); |
| 95 | + for(var j = 0; j < 4; j++) |
| 96 | + { |
| 97 | + if(i * 8 + j * 6 > input.length * 8) output += this.b64pad; |
| 98 | + else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F); |
| 99 | + } |
| 100 | + } |
| 101 | + return output; |
| 102 | +}, |
| 103 | + |
| 104 | +/* |
| 105 | + * Convert a raw string to an arbitrary string encoding |
| 106 | + */ |
| 107 | +rstr2any : function(input, encoding) { |
| 108 | + var divisor = encoding.length; |
| 109 | + var i, j, q, x, quotient; |
| 110 | + |
| 111 | + /* Convert to an array of 16-bit big-endian values, forming the dividend */ |
| 112 | + var dividend = Array(Math.ceil(input.length / 2)); |
| 113 | + for(i = 0; i < dividend.length; i++) |
| 114 | + { |
| 115 | + dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1); |
| 116 | + } |
| 117 | + |
| 118 | + /* |
| 119 | + * Repeatedly perform a long division. The binary array forms the dividend, |
| 120 | + * the length of the encoding is the divisor. Once computed, the quotient |
| 121 | + * forms the dividend for the next step. All remainders are stored for later |
| 122 | + * use. |
| 123 | + */ |
| 124 | + var full_length = Math.ceil(input.length * 8 / |
| 125 | + (Math.log(encoding.length) / Math.log(2))); |
| 126 | + var remainders = Array(full_length); |
| 127 | + for(j = 0; j < full_length; j++) |
| 128 | + { |
| 129 | + quotient = Array(); |
| 130 | + x = 0; |
| 131 | + for(i = 0; i < dividend.length; i++) |
| 132 | + { |
| 133 | + x = (x << 16) + dividend[i]; |
| 134 | + q = Math.floor(x / divisor); |
| 135 | + x -= q * divisor; |
| 136 | + if(quotient.length > 0 || q > 0) |
| 137 | + quotient[quotient.length] = q; |
| 138 | + } |
| 139 | + remainders[j] = x; |
| 140 | + dividend = quotient; |
| 141 | + } |
| 142 | + |
| 143 | + /* Convert the remainders to the output string */ |
| 144 | + var output = ""; |
| 145 | + for(i = remainders.length - 1; i >= 0; i--) |
| 146 | + output += encoding.charAt(remainders[i]); |
| 147 | + |
| 148 | + return output; |
| 149 | +}, |
| 150 | + |
| 151 | +/* |
| 152 | + * Encode a string as utf-8. |
| 153 | + * For efficiency, this assumes the input is valid utf-16. |
| 154 | + */ |
| 155 | +str2rstr_utf8 : function(input) { |
| 156 | + var output = ""; |
| 157 | + var i = -1; |
| 158 | + var x, y; |
| 159 | + |
| 160 | + while(++i < input.length) |
| 161 | + { |
| 162 | + /* Decode utf-16 surrogate pairs */ |
| 163 | + x = input.charCodeAt(i); |
| 164 | + y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0; |
| 165 | + if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF) |
| 166 | + { |
| 167 | + x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF); |
| 168 | + i++; |
| 169 | + } |
| 170 | + |
| 171 | + /* Encode output as utf-8 */ |
| 172 | + if(x <= 0x7F) |
| 173 | + output += String.fromCharCode(x); |
| 174 | + else if(x <= 0x7FF) |
| 175 | + output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F), |
| 176 | + 0x80 | ( x & 0x3F)); |
| 177 | + else if(x <= 0xFFFF) |
| 178 | + output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F), |
| 179 | + 0x80 | ((x >>> 6 ) & 0x3F), |
| 180 | + 0x80 | ( x & 0x3F)); |
| 181 | + else if(x <= 0x1FFFFF) |
| 182 | + output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07), |
| 183 | + 0x80 | ((x >>> 12) & 0x3F), |
| 184 | + 0x80 | ((x >>> 6 ) & 0x3F), |
| 185 | + 0x80 | ( x & 0x3F)); |
| 186 | + } |
| 187 | + return output; |
| 188 | +}, |
| 189 | + |
| 190 | +/* |
| 191 | + * Encode a string as utf-16 |
| 192 | + */ |
| 193 | +str2rstr_utf16le : function(input) { |
| 194 | + var output = ""; |
| 195 | + for(var i = 0; i < input.length; i++) |
| 196 | + output += String.fromCharCode( input.charCodeAt(i) & 0xFF, |
| 197 | + (input.charCodeAt(i) >>> 8) & 0xFF); |
| 198 | + return output; |
| 199 | +}, |
| 200 | + |
| 201 | +str2rstr_utf16be : function(input) { |
| 202 | + var output = ""; |
| 203 | + for(var i = 0; i < input.length; i++) |
| 204 | + output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF, |
| 205 | + input.charCodeAt(i) & 0xFF); |
| 206 | + return output; |
| 207 | +}, |
| 208 | + |
| 209 | +/* |
| 210 | + * Convert a raw string to an array of little-endian words |
| 211 | + * Characters >255 have their high-byte silently ignored. |
| 212 | + */ |
| 213 | +rstr2binl : function(input) { |
| 214 | + var output = Array(input.length >> 2); |
| 215 | + for(var i = 0; i < output.length; i++) |
| 216 | + output[i] = 0; |
| 217 | + for(var i = 0; i < input.length * 8; i += 8) |
| 218 | + output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (i%32); |
| 219 | + return output; |
| 220 | +}, |
| 221 | + |
| 222 | +/* |
| 223 | + * Convert an array of little-endian words to a string |
| 224 | + */ |
| 225 | +binl2rstr : function(input) { |
| 226 | + var output = ""; |
| 227 | + for(var i = 0; i < input.length * 32; i += 8) |
| 228 | + output += String.fromCharCode((input[i>>5] >>> (i % 32)) & 0xFF); |
| 229 | + return output; |
| 230 | +}, |
| 231 | + |
| 232 | +/* |
| 233 | + * Calculate the MD5 of an array of little-endian words, and a bit length. |
| 234 | + */ |
| 235 | +binl : function(x, len) { |
| 236 | + /* append padding */ |
| 237 | + x[len >> 5] |= 0x80 << ((len) % 32); |
| 238 | + x[(((len + 64) >>> 9) << 4) + 14] = len; |
| 239 | + |
| 240 | + var a = 1732584193; |
| 241 | + var b = -271733879; |
| 242 | + var c = -1732584194; |
| 243 | + var d = 271733878; |
| 244 | + |
| 245 | + for(var i = 0; i < x.length; i += 16) |
| 246 | + { |
| 247 | + var olda = a; |
| 248 | + var oldb = b; |
| 249 | + var oldc = c; |
| 250 | + var oldd = d; |
| 251 | + |
| 252 | + a = this.md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); |
| 253 | + d = this.md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); |
| 254 | + c = this.md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); |
| 255 | + b = this.md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); |
| 256 | + a = this.md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); |
| 257 | + d = this.md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); |
| 258 | + c = this.md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); |
| 259 | + b = this.md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); |
| 260 | + a = this.md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); |
| 261 | + d = this.md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); |
| 262 | + c = this.md5_ff(c, d, a, b, x[i+10], 17, -42063); |
| 263 | + b = this.md5_ff(b, c, d, a, x[i+11], 22, -1990404162); |
| 264 | + a = this.md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); |
| 265 | + d = this.md5_ff(d, a, b, c, x[i+13], 12, -40341101); |
| 266 | + c = this.md5_ff(c, d, a, b, x[i+14], 17, -1502002290); |
| 267 | + b = this.md5_ff(b, c, d, a, x[i+15], 22, 1236535329); |
| 268 | + |
| 269 | + a = this.md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); |
| 270 | + d = this.md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); |
| 271 | + c = this.md5_gg(c, d, a, b, x[i+11], 14, 643717713); |
| 272 | + b = this.md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); |
| 273 | + a = this.md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); |
| 274 | + d = this.md5_gg(d, a, b, c, x[i+10], 9 , 38016083); |
| 275 | + c = this.md5_gg(c, d, a, b, x[i+15], 14, -660478335); |
| 276 | + b = this.md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); |
| 277 | + a = this.md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); |
| 278 | + d = this.md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); |
| 279 | + c = this.md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); |
| 280 | + b = this.md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); |
| 281 | + a = this.md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); |
| 282 | + d = this.md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); |
| 283 | + c = this.md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); |
| 284 | + b = this.md5_gg(b, c, d, a, x[i+12], 20, -1926607734); |
| 285 | + |
| 286 | + a = this.md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); |
| 287 | + d = this.md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); |
| 288 | + c = this.md5_hh(c, d, a, b, x[i+11], 16, 1839030562); |
| 289 | + b = this.md5_hh(b, c, d, a, x[i+14], 23, -35309556); |
| 290 | + a = this.md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); |
| 291 | + d = this.md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); |
| 292 | + c = this.md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); |
| 293 | + b = this.md5_hh(b, c, d, a, x[i+10], 23, -1094730640); |
| 294 | + a = this.md5_hh(a, b, c, d, x[i+13], 4 , 681279174); |
| 295 | + d = this.md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); |
| 296 | + c = this.md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); |
| 297 | + b = this.md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); |
| 298 | + a = this.md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); |
| 299 | + d = this.md5_hh(d, a, b, c, x[i+12], 11, -421815835); |
| 300 | + c = this.md5_hh(c, d, a, b, x[i+15], 16, 530742520); |
| 301 | + b = this.md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); |
| 302 | + |
| 303 | + a = this.md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); |
| 304 | + d = this.md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); |
| 305 | + c = this.md5_ii(c, d, a, b, x[i+14], 15, -1416354905); |
| 306 | + b = this.md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); |
| 307 | + a = this.md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); |
| 308 | + d = this.md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); |
| 309 | + c = this.md5_ii(c, d, a, b, x[i+10], 15, -1051523); |
| 310 | + b = this.md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); |
| 311 | + a = this.md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); |
| 312 | + d = this.md5_ii(d, a, b, c, x[i+15], 10, -30611744); |
| 313 | + c = this.md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); |
| 314 | + b = this.md5_ii(b, c, d, a, x[i+13], 21, 1309151649); |
| 315 | + a = this.md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); |
| 316 | + d = this.md5_ii(d, a, b, c, x[i+11], 10, -1120210379); |
| 317 | + c = this.md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); |
| 318 | + b = this.md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); |
| 319 | + |
| 320 | + a = this.safe_add(a, olda); |
| 321 | + b = this.safe_add(b, oldb); |
| 322 | + c = this.safe_add(c, oldc); |
| 323 | + d = this.safe_add(d, oldd); |
| 324 | + } |
| 325 | + return Array(a, b, c, d); |
| 326 | +}, |
| 327 | + |
| 328 | +/* |
| 329 | + * These functions implement the four basic operations the algorithm uses. |
| 330 | + */ |
| 331 | +md5_cmn : function(q, a, b, x, s, t) { |
| 332 | + return this.safe_add(this.bit_rol(this.safe_add(this.safe_add(a, q), this.safe_add(x, t)), s),b); |
| 333 | +}, |
| 334 | +md5_ff : function(a, b, c, d, x, s, t) { |
| 335 | + return this.md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); |
| 336 | +}, |
| 337 | +md5_gg : function(a, b, c, d, x, s, t) { |
| 338 | + return this.md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); |
| 339 | +}, |
| 340 | +md5_hh : function(a, b, c, d, x, s, t) { |
| 341 | + return this.md5_cmn(b ^ c ^ d, a, b, x, s, t); |
| 342 | +}, |
| 343 | +md5_ii : function(a, b, c, d, x, s, t) { |
| 344 | + return this.md5_cmn(c ^ (b | (~d)), a, b, x, s, t); |
| 345 | +}, |
| 346 | + |
| 347 | +/* |
| 348 | + * Add integers, wrapping at 2^32. This uses 16-bit operations internally |
| 349 | + * to work around bugs in some JS interpreters. |
| 350 | + */ |
| 351 | +safe_add : function(x, y) { |
| 352 | + var lsw = (x & 0xFFFF) + (y & 0xFFFF); |
| 353 | + var msw = (x >> 16) + (y >> 16) + (lsw >> 16); |
| 354 | + return (msw << 16) | (lsw & 0xFFFF); |
| 355 | +}, |
| 356 | + |
| 357 | +/* |
| 358 | + * Bitwise rotate a 32-bit number to the left. |
| 359 | + */ |
| 360 | +bit_rol : function(num, cnt) { |
| 361 | + return (num << cnt) | (num >>> (32 - cnt)); |
| 362 | +} |
| 363 | + |
| 364 | +} |
\ No newline at end of file |
Property changes on: trunk/extensions/WikiSync/md5.js |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 365 | + native |
Index: trunk/extensions/WikiSync/WikiSync.i18n.php |
— | — | @@ -19,12 +19,19 @@ |
20 | 20 | 'wikisync_clear_log' => 'Clear log', |
21 | 21 | 'wikisync_login_to_remote_wiki' => 'Login to remote wiki', |
22 | 22 | 'wikisync_remote_wiki_root' => 'Remote wiki root', |
23 | | - 'wikisync_remote_wiki_example' => 'Path to api.php, for example: http://www.mediawiki.org/w', |
| 23 | + 'wikisync_remote_wiki_example' => 'path to api.php, for example: http://www.mediawiki.org/w', |
24 | 24 | 'wikisync_remote_wiki_user' => 'Remote wiki user name', |
25 | 25 | 'wikisync_remote_wiki_pass' => 'Remote wiki password', |
26 | 26 | 'wikisync_remote_login_button' => 'Log in', |
27 | 27 | 'wikisync_sync_files' => 'Synchronize files', |
| 28 | + 'wikisync_store_password' => 'Store remote wiki password', |
28 | 29 | 'wikisync_synchronization_button' => 'Synchronize', |
| 30 | + 'wikisync_scheduler_log' => 'Scheduler log', |
| 31 | + 'wikisync_scheduler_setup' => 'Scheduler setup', |
| 32 | + 'wikisync_scheduler_turn_on' => 'Turn on the scheduler', |
| 33 | + 'wikisync_scheduler_switch_direction' => 'Automatically switch the direction of synchronization', |
| 34 | + 'wikisync_scheduler_time_interval' => 'Time in minutes between automatical synchronizations', |
| 35 | + 'wikisync_apply_button' => 'Apply', |
29 | 36 | 'wikisync_log_imported_by' => 'Imported by [[Special:WikiSync|WikiSync]]', |
30 | 37 | 'wikisync_log_uploaded_by' => 'Uploaded by [[Special:WikiSync|WikiSync]]', |
31 | 38 | 'wikisync_api_result_unknown_action' => 'Unknown API action', |
— | — | @@ -36,7 +43,7 @@ |
37 | 44 | 'wikisync_api_result_NoName' => 'You did not set the lgname parameter', |
38 | 45 | 'wikisync_api_result_Illegal' => 'You provided an illegal username', |
39 | 46 | 'wikisync_api_result_NotExists' => 'The username you provided does not exist', |
40 | | - 'wikisync_api_result_EmptyPass' => 'You did not set the lgpassword parameter or you left it empty', |
| 47 | + 'wikisync_api_result_EmptyPass' => 'You didn\'t set the lgpassword parameter or you left it empty', |
41 | 48 | 'wikisync_api_result_WrongPass' => 'The password you provided is incorrect', |
42 | 49 | 'wikisync_api_result_WrongPluginPass' => 'The password you provided is incorrect', |
43 | 50 | '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,7 +51,7 @@ |
45 | 52 | 'wikisync_api_result_Blocked' => 'User is blocked', |
46 | 53 | 'wikisync_api_result_mustbeposted' => 'The login module requires a POST request', |
47 | 54 | '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', |
| 55 | + 'wikisync_api_result_no_import_rights' => 'This user is not allowed to import xml dump files', |
49 | 56 | 'wikisync_api_result_Success' => 'Successfully logged into remote wiki site', |
50 | 57 | 'wikisync_js_last_op_error' => 'Last operation returned an error. |
51 | 58 | |
— | — | @@ -65,9 +72,16 @@ |
66 | 73 | 'wikisync_js_sync_to_itself' => 'You cannot synchronize the wiki to itself', |
67 | 74 | 'wikisync_js_diff_search' => 'Looking for difference in destination revisions', |
68 | 75 | 'wikisync_js_revision' => 'Revision $1', |
69 | | - '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.', // FIXME: needs plural support. |
| 76 | + 'wikisync_js_file_size_mismatch' => 'Temporary file "$1" size ($2 {{PLURAL:$2|byte|bytes}}) does not match required size ($3 {{PLURAL:$3|byte|bytes}}). Make sure the file "$4" was not manually overwritten in repository of source wiki.', |
| 77 | + 'wikisync_js_invalid_scheduler_time' => 'Scheduler time must be a positive integer number', |
| 78 | + 'wikisync_js_scheduler_countdown' => '$1 {{PLURAL:$1|minute|minutes}} left', |
| 79 | + 'wikisync_js_sync_start_ltr' => 'Starting the synchronization from local wiki to remote wiki at $1', |
| 80 | + 'wikisync_js_sync_start_rtl' => 'Starting the synchronization from remote wiki to local wiki at $1', |
| 81 | + 'wikisync_js_sync_end_ltr' => 'Finished the synchronization from local wiki to remote wiki at $1', |
| 82 | + 'wikisync_js_sync_end_rtl' => 'Finished the synchronization from remote wiki to local wiki at $1', |
70 | 83 | ); |
71 | 84 | |
| 85 | + |
72 | 86 | /** Message documentation (Message documentation) |
73 | 87 | * @author Тест |
74 | 88 | */ |
— | — | @@ -518,6 +532,12 @@ |
519 | 533 | 'wikisync_local_root' => 'Корневой адрес локального сайта', |
520 | 534 | 'wikisync_remote_root' => 'Корневой адрес удалённого сайта', |
521 | 535 | 'wikisync_remote_log' => 'Журнал удалённых действий', |
| 536 | + 'wikisync_scheduler_log' => 'Журнал планировщика', |
| 537 | + 'wikisync_scheduler_setup' => 'Настройки планировщика', |
| 538 | + 'wikisync_scheduler_turn_on' => 'Включить планировщик', |
| 539 | + 'wikisync_scheduler_switch_direction' => 'Автоматически изменять направление синхронизации', |
| 540 | + 'wikisync_scheduler_time_interval' => 'Количество минут между автоматическими синхронизациями', |
| 541 | + 'wikisync_apply_button' => 'Применить', |
522 | 542 | 'wikisync_clear_log' => 'Очистить журнал', |
523 | 543 | 'wikisync_login_to_remote_wiki' => 'Зайти на удалённый сайт', |
524 | 544 | 'wikisync_remote_wiki_root' => 'Корневой адрес удалённого сайта', |
— | — | @@ -526,6 +546,7 @@ |
527 | 547 | 'wikisync_remote_wiki_pass' => 'Пароль на удалённом сайте', |
528 | 548 | 'wikisync_remote_login_button' => 'Зайти', |
529 | 549 | 'wikisync_sync_files' => 'Синхронизировать файлы', |
| 550 | + 'wikisync_store_password' => 'Сохранить пароль удалённого сайта', |
530 | 551 | 'wikisync_synchronization_button' => 'Синхронизировать', |
531 | 552 | 'wikisync_log_imported_by' => 'Импортировано с помощью [[Special:WikiSync]]', |
532 | 553 | 'wikisync_log_uploaded_by' => 'Загружено с помощью [[Special:WikiSync]]', |
— | — | @@ -552,7 +573,13 @@ |
553 | 574 | 'wikisync_js_sync_to_itself' => 'Невозможно синхронизировать вики сайт сам в себя', |
554 | 575 | 'wikisync_js_diff_search' => 'Поиск отличий в ревизиях вики-сайта назначения', |
555 | 576 | 'wikisync_js_revision' => 'Ревизия $1', |
556 | | - 'wikisync_js_file_size_mismatch' => 'Размер временного файла $1 ($2 байт) не соответствует требуемому размеру файла ($3 байт). Пожалуйста убедитесь, что файл $4 не был переписан вручную в репозиторий исходного вики-сайта.', |
| 577 | + 'wikisync_js_file_size_mismatch' => 'Размер временного файла "$1" ($2 {{PLURAL:$2|байт|байта|байтов}}) не соответствует требуемому размеру файла ($3 {{PLURAL:$3|байт|байта|байтов}}). Пожалуйста убедитесь, что файл "$4" не был переписан вручную в репозиторий исходного вики-сайта.', |
| 578 | + 'wikisync_js_invalid_scheduler_time' => 'Время планировщика должно быть положительным целым числом', |
| 579 | + 'wikisync_js_scheduler_countdown' => 'Осталось $1 {{PLURAL:$1|минута|минуты|минут}}', |
| 580 | + 'wikisync_js_sync_start_ltr' => 'Запуск синхронизации с локального вики-сайта на удалённый $1', |
| 581 | + 'wikisync_js_sync_start_rtl' => 'Запуск синхронизации с удалённого вики-сайта на локальный $1', |
| 582 | + 'wikisync_js_sync_end_ltr' => 'Окончание синхронизации с локального вики-сайта на удалённый $1', |
| 583 | + 'wikisync_js_sync_end_rtl' => 'Окончание синхронизации с удалённого вики-сайта на локальный $1', |
557 | 584 | ); |
558 | 585 | |
559 | 586 | /** Telugu (తెలుగు) |
— | — | @@ -579,4 +606,3 @@ |
580 | 607 | 'wikisync_js_sync_to_itself' => 'Ви не можете синхронізувати вікі саму до себе', |
581 | 608 | 'wikisync_js_revision' => 'Версія $1', |
582 | 609 | ); |
583 | | - |
Index: trunk/extensions/WikiSync/INSTALL |
— | — | @@ -1,4 +1,4 @@ |
2 | | -MediaWiki extension WikiSync, version 0.2.1 |
| 2 | +MediaWiki extension WikiSync, version 0.3.1 |
3 | 3 | |
4 | 4 | * download the latest available version and extract it to your wiki extension directory. |
5 | 5 | * add the following line to LocalSettings.php |
Index: trunk/extensions/WikiSync/WikiSync.js |
— | — | @@ -27,16 +27,99 @@ |
28 | 28 | * * Add this line at the end of your LocalSettings.php file : |
29 | 29 | * require_once "$IP/extensions/WikiSync/WikiSync.php"; |
30 | 30 | * |
31 | | - * @version 0.2.1 |
| 31 | + * @version 0.3.1 |
32 | 32 | * @link http://www.mediawiki.org/wiki/Extension:WikiSync |
33 | 33 | * @author Dmitriy Sintsov <questpc@rambler.ru> |
34 | 34 | * @addtogroup Extensions |
35 | 35 | */ |
36 | 36 | |
| 37 | +window.WikiSyncScheduler = { |
| 38 | + |
| 39 | + // {{{ automatical synchronization settings |
| 40 | + isOn: false, |
| 41 | + switchDirection : false, |
| 42 | + timeout : 300, // default timeout: amount of 1/10 of minutes (30 minutes) |
| 43 | + currTimeout : 300, // current timeout: amount of 1/10 of minutes (30 minutes) |
| 44 | + pollTimeout : 6000, // polling once per 1/10 of minute (setTimeout value in milliseconds) |
| 45 | + // }}} |
| 46 | + |
| 47 | + counterNode : null, // DOM object which inner text node will contain countdown number |
| 48 | + syncButton : null, // DOM object input which 'disabled' property indicates that |
| 49 | + // synchronization is already going (scheduling is impossible) |
| 50 | + |
| 51 | + setup : function( form ) { |
| 52 | + this.counterNode = document.getElementById( 'ws_scheduler_countdown' ); |
| 53 | + this.syncButton = document.getElementById( 'wikisync_synchronization_button' ); |
| 54 | + var timeInterval = form.ws_auto_sync_time_interval; |
| 55 | + if ( timeInterval.value == '' ) { |
| 56 | + timeInterval.value = this.timeout; |
| 57 | + } |
| 58 | + var time = parseInt( timeInterval.value ); |
| 59 | + if ( isNaN( time ) || time <= 0 ) { |
| 60 | + alert( WikiSync.formatMessage( 'invalid_scheduler_time' ) ); |
| 61 | + return false; |
| 62 | + } |
| 63 | + time *= 10; // form has value in minutes; poll will use value in 1/10 of minutes |
| 64 | + this.isOn = form.ws_auto_sync.checked; |
| 65 | + WikiSyncUtils.setCookie( 'auto_sync', this.isOn, WikiSync.cookieExpireTime, '/' ); |
| 66 | + this.switchDirection = form.ws_auto_switch_direction.checked; |
| 67 | + WikiSyncUtils.setCookie( 'auto_switch_direction', this.switchDirection, WikiSync.cookieExpireTime, '/' ); |
| 68 | + this.timeout = time; |
| 69 | + WikiSyncUtils.setCookie( 'auto_sync_time_interval', timeInterval.value, WikiSync.cookieExpireTime, '/' ); |
| 70 | + this.reset(); |
| 71 | + return false; |
| 72 | + }, |
| 73 | + |
| 74 | + reset : function() { |
| 75 | + this.currTimeout = this.timeout; |
| 76 | + this.displayCounter(); |
| 77 | + }, |
| 78 | + |
| 79 | + displayCounter : function() { |
| 80 | + if ( this.isOn && !this.syncButton.disabled ) { |
| 81 | + // display current timeout |
| 82 | + var textNode = document.createTextNode( WikiSync.formatMessage( 'scheduler_countdown', Math.ceil( this.currTimeout / 10 ) ) ); |
| 83 | + if ( this.counterNode.firstChild === null ) { |
| 84 | + this.counterNode.appendChild( textNode ); |
| 85 | + } else { |
| 86 | + this.counterNode.replaceChild( textNode, this.counterNode.firstChild ); |
| 87 | + } |
| 88 | + } else { |
| 89 | + this.counterNode.innerHTML = ''; |
| 90 | + } |
| 91 | + }, |
| 92 | + |
| 93 | + poll : function() { |
| 94 | + if ( !this.isOn ) { |
| 95 | + this.reset(); |
| 96 | + } else { |
| 97 | + // scheduler is active |
| 98 | + if ( this.syncButton.disabled ) { |
| 99 | + // not logged in or synchronization is already in progress |
| 100 | + this.reset(); |
| 101 | + } else { |
| 102 | + // scheduling is possible (logged in and no sync is already going) |
| 103 | + if ( this.currTimeout > 0 ) { |
| 104 | + // some time is left |
| 105 | + this.displayCounter(); |
| 106 | + this.currTimeout--; |
| 107 | + } else { |
| 108 | + // time is out, let's go |
| 109 | + WikiSync.process(); |
| 110 | + } |
| 111 | + } |
| 112 | + } |
| 113 | + window.setTimeout( function() { WikiSyncScheduler.poll(); }, this.pollTimeout ); |
| 114 | + } |
| 115 | + |
| 116 | +}; |
| 117 | + |
37 | 118 | window.WikiSync = { |
38 | 119 | |
39 | 120 | _WikiSync : '', // WikiSync context |
40 | 121 | |
| 122 | + cookieExpireTime : 30 * 24 * 60 * 60, // see also WikiSync.php, WikiSync::COOKIE_EXPIRE_TIME |
| 123 | + |
41 | 124 | // by default, synchronize from remote to local |
42 | 125 | directionToLocal : true, |
43 | 126 | |
— | — | @@ -101,6 +184,7 @@ |
102 | 185 | // these are not initialized in 1.17+ codepath |
103 | 186 | localMessages : null, |
104 | 187 | |
| 188 | + // used only when ResourceLoader is not available ( MediaWiki < 1.17) |
105 | 189 | setLocalMessages : function( localMessages ) { |
106 | 190 | this.localMessages = localMessages; |
107 | 191 | }, |
— | — | @@ -109,37 +193,42 @@ |
110 | 194 | // in case of future ResourceLoader adoption in Extension:CategoryBrowser there |
111 | 195 | // should be few methods with different prefixes instead of just one |
112 | 196 | var prefix = 'wikisync_js_'; |
| 197 | + var formatted; |
113 | 198 | if ( typeof mediaWiki === 'object' && |
114 | 199 | typeof mediaWiki.msg === 'function' ) { |
115 | 200 | // MW 1.17+ |
116 | 201 | var args = arguments; |
117 | 202 | args[0] = prefix + args[0]; |
118 | | - return mediaWiki.msg.apply( mediaWiki.msg, args ); |
119 | | - } |
120 | | - var formatted = this.localMessages[ prefix + arguments[0] ]; |
121 | | - var indexes = []; |
122 | | - var pos; |
123 | | - var j; |
124 | | - // gettting $n parameter indexes |
125 | | - // going in reverse order is very important for the second for loop to be correct |
126 | | - for ( var i = arguments.length - 1; i > 0; i-- ) { |
127 | | - if ( ( pos = formatted.indexOf( '$' + i ) ) !== -1 ) { |
128 | | - indexes.push( pos ); |
| 203 | + formatted = mediaWiki.msg.apply( mediaWiki.msg, args ); |
| 204 | + // probably will not be required when mediaWiki.msg will support plurals |
| 205 | + // todo: figure out how to call replace before msg.apply |
| 206 | + formatted = formatted.replace( /{{PLURAL:.+?\|(.+?)\|.+?}}/g, '$1' ); |
| 207 | + } else { |
| 208 | + // MW < 1.17 |
| 209 | + formatted = this.localMessages[ prefix + arguments[0] ]; |
| 210 | + // replace plurals to their singular values |
| 211 | + // implementing something better is not so easy task, because plurals vary between languages |
| 212 | + formatted = formatted.replace( /{{PLURAL:.+?\|(.+?)\|.+?}}/g, '$1' ); |
| 213 | + var indexes = []; |
| 214 | + var pos; |
| 215 | + var j; |
| 216 | + // getting $n parameter indexes |
| 217 | + // going in reverse order is very important for the second for loop to be correct |
| 218 | + for ( var i = arguments.length - 1; i > 0; i-- ) { |
| 219 | + if ( ( pos = formatted.indexOf( '$' + i ) ) !== -1 ) { |
| 220 | + indexes.push( pos ); |
| 221 | + } |
129 | 222 | } |
| 223 | + // substituting the parameters |
| 224 | + for ( i = 0; i < indexes.length; i++ ) { |
| 225 | + pos = indexes[i]; |
| 226 | + j = formatted.charAt( pos + 1 ); |
| 227 | + formatted = formatted.slice( 0, pos ) + arguments[j] + formatted.slice( pos + 2 ); |
| 228 | + } |
130 | 229 | } |
131 | | - // substituting the parameters |
132 | | - for ( i = 0; i < indexes.length; i++ ) { |
133 | | - pos = indexes[i]; |
134 | | - j = formatted.charAt( pos + 1 ); |
135 | | - formatted = formatted.slice( 0, pos ) + arguments[j] + formatted.slice( pos + 2 ); |
136 | | - } |
137 | 230 | return formatted; |
138 | 231 | }, |
139 | 232 | |
140 | | - mathLogBase : function( x, base ) { |
141 | | - return Math.log( x ) / Math.log( base ); |
142 | | - }, |
143 | | - |
144 | 233 | showIframe : function( url ) { |
145 | 234 | var text = document.createTextNode( url ); |
146 | 235 | var locElem = document.getElementById( 'wikisync_iframe_location' ); |
— | — | @@ -153,24 +242,12 @@ |
154 | 243 | iframe.src = url; |
155 | 244 | }, |
156 | 245 | |
157 | | - log : function( s, color, type ) { |
158 | | - var logContainer = document.getElementById( 'wikisync_remote_log' ); |
159 | | - var span = document.createElement( 'SPAN' ); |
160 | | - if ( typeof s === 'object' ) { |
161 | | - s = JSON.stringify( s ); |
| 246 | + log : function() { |
| 247 | + // sometimes, when user clicks the "Log in" button too fast, |
| 248 | + // operationLogger might be still uninitialized in onloadHandler |
| 249 | + if ( typeof this.operationLogger !== 'undefined' ) { |
| 250 | + this.operationLogger.log.apply( this.operationLogger, arguments ); |
162 | 251 | } |
163 | | - if ( typeof type !== 'undefined' ) { |
164 | | - var b = document.createElement( 'B' ); |
165 | | - b.appendChild( document.createTextNode( type + ': ' ) ); |
166 | | - span.appendChild( b ); |
167 | | - } |
168 | | - span.appendChild( document.createTextNode( s ) ); |
169 | | - if ( typeof color !== 'undefined' ) { |
170 | | - span.style.color = color; |
171 | | - } |
172 | | - logContainer.appendChild( span ); |
173 | | - logContainer.appendChild( document.createElement( 'HR' ) ); |
174 | | - logContainer.scrollTop = logContainer.scrollHeight; |
175 | 252 | }, |
176 | 253 | |
177 | 254 | sourceLog : function( s, type ) { |
— | — | @@ -189,11 +266,6 @@ |
190 | 267 | this.log( s, 'maroon', t ); |
191 | 268 | }, |
192 | 269 | |
193 | | - clearLog : function() { |
194 | | - document.getElementById( 'wikisync_remote_log' ).innerHTML = ''; |
195 | | - return false; |
196 | | - }, |
197 | | - |
198 | 270 | error : function( s, type ) { |
199 | 271 | if ( typeof type !== 'undefined' ) { |
200 | 272 | this.log( s, 'red', type ); |
— | — | @@ -202,7 +274,12 @@ |
203 | 275 | } |
204 | 276 | }, |
205 | 277 | |
206 | | - setDirection : function( eventObj ) { |
| 278 | + clearLog : function( id ) { |
| 279 | + document.getElementById( id ).innerHTML = ''; |
| 280 | + return false; |
| 281 | + }, |
| 282 | + |
| 283 | + switchDirection : function( eventObj ) { |
207 | 284 | eventObj.blur(); |
208 | 285 | if ( this.directionToLocal = !this.directionToLocal ) { |
209 | 286 | eventObj.value = '<='; |
— | — | @@ -212,43 +289,36 @@ |
213 | 290 | return false; |
214 | 291 | }, |
215 | 292 | |
216 | | - setSyncFiles : function( eventObj ) { |
| 293 | + blurElement : function( eventObj ) { |
217 | 294 | eventObj.blur(); |
218 | | - this.syncFiles = eventObj.checked; |
219 | 295 | return false; |
220 | 296 | }, |
221 | 297 | |
222 | | - remoteRootChange : function( eventObj ) { |
223 | | - var textNode = document.createTextNode( eventObj.value ); |
224 | | - var wrr = document.getElementById( 'wikisync_remote_root' ); |
225 | | - wrr.replaceChild( textNode , wrr.firstChild ); |
226 | | - return false; |
227 | | - }, |
228 | | - |
229 | | - submitRemoteLogin : function( form ) { |
230 | | - this.remoteContext.wikiroot = form.remote_wiki_root.value; |
231 | | - this.setSyncFiles( form.ws_sync_files ); |
232 | | - sajax_do_call( 'WikiSyncClient::remoteLogin', |
233 | | - [this.remoteContext.wikiroot, form.remote_wiki_user.value, form.remote_wiki_pass.value], |
234 | | - WikiSync.remoteLogin ); |
235 | | - return false; |
236 | | - }, |
237 | | - |
238 | | - /* |
239 | | - * initializes everything in remoteContext except of wikiroot |
| 298 | + /** |
| 299 | + * called when synchronization was done succesfully |
240 | 300 | */ |
241 | | - setRemoteContext : function( login ) { |
242 | | - this.remoteContext.userid = login.userid; |
243 | | - this.remoteContext.username = login.username; |
244 | | - this.remoteContext.logintoken = login.token; |
245 | | - this.remoteContext.cookieprefix = login.cookieprefix; |
246 | | - this.remoteContext.sessionid = login.sessionid; |
| 301 | + successHandler : function( msg ) { |
| 302 | + // enable all buttons |
| 303 | + this.schedulerLogger.log( |
| 304 | + this.formatMessage( |
| 305 | + this.directionToLocal ? 'sync_end_rtl' : 'sync_end_ltr', |
| 306 | + WikiSyncUtils.getLocalDate() |
| 307 | + ) |
| 308 | + ); |
| 309 | + this.setButtons( true ); |
| 310 | + // check, whether new scheduling is needed |
| 311 | + if ( WikiSyncScheduler.isOn ) { |
| 312 | + // do not make operation log grew too long |
| 313 | + // with automatical sycnronizations |
| 314 | + this.operationLogger.clear(); |
| 315 | + if ( WikiSyncScheduler.switchDirection ) { |
| 316 | + this.switchDirection( document.getElementById( 'wikisync_direction_button' ) ); |
| 317 | + } |
| 318 | + } else { |
| 319 | + alert( msg ); |
| 320 | + } |
247 | 321 | }, |
248 | 322 | |
249 | | - getResponseError : function( request ) { |
250 | | - return 'status=' + request.status + ', text=' + request.responseText; |
251 | | - }, |
252 | | - |
253 | 323 | /** |
254 | 324 | * enables or disables UI buttons |
255 | 325 | * arguments[0] - boolean true to disable selected buttons, false to enable |
— | — | @@ -272,7 +342,38 @@ |
273 | 343 | } |
274 | 344 | }, |
275 | 345 | |
| 346 | + remoteRootChange : function( eventObj ) { |
| 347 | + var textNode = document.createTextNode( eventObj.value ); |
| 348 | + var wrr = document.getElementById( 'wikisync_remote_root' ); |
| 349 | + wrr.replaceChild( textNode , wrr.firstChild ); |
| 350 | + return false; |
| 351 | + }, |
| 352 | + |
276 | 353 | /* |
| 354 | + * initializes everything in remoteContext except of wikiroot |
| 355 | + */ |
| 356 | + setRemoteContext : function( login ) { |
| 357 | + this.remoteContext.userid = login.userid; |
| 358 | + this.remoteContext.username = login.username; |
| 359 | + this.remoteContext.logintoken = login.token; |
| 360 | + this.remoteContext.cookieprefix = login.cookieprefix; |
| 361 | + this.remoteContext.sessionid = login.sessionid; |
| 362 | + }, |
| 363 | + |
| 364 | + getResponseError : function( request ) { |
| 365 | + return 'status=' + request.status + ', text=' + request.responseText; |
| 366 | + }, |
| 367 | + |
| 368 | + submitRemoteLogin : function( form ) { |
| 369 | + this.remoteContext.wikiroot = form.remote_wiki_root.value; |
| 370 | + this.syncFiles = form.ws_sync_files.checked; |
| 371 | + sajax_do_call( 'WikiSyncClient::remoteLogin', |
| 372 | + [this.remoteContext.wikiroot, form.remote_wiki_user.value, form.remote_wiki_pass.value, form.ws_store_password.checked], |
| 373 | + WikiSync.remoteLogin ); |
| 374 | + return false; |
| 375 | + }, |
| 376 | + |
| 377 | + /* |
277 | 378 | * @param request.responsetext - login "final" response |
278 | 379 | */ |
279 | 380 | remoteLogin : function( request ) { |
— | — | @@ -709,7 +810,8 @@ |
710 | 811 | case 'start' : |
711 | 812 | this.srcSyncId = operation.revid; |
712 | 813 | this.showIframe( this.srcWikiRoot + '/index.php?oldid=' + operation.revid ); |
713 | | - if ( !confirm( this.formatMessage( 'synchronization_confirmation', this.srcWikiRoot, this.dstWikiRoot, operation.revid ) ) ) { |
| 814 | + if ( !WikiSyncScheduler.isOn && |
| 815 | + !confirm( this.formatMessage( 'synchronization_confirmation', this.srcWikiRoot, this.dstWikiRoot, operation.revid ) ) ) { |
714 | 816 | this.log( 'Operation was cancelled' ); |
715 | 817 | this.syncPercents.reset(); |
716 | 818 | // enable all buttons |
— | — | @@ -770,9 +872,8 @@ |
771 | 873 | case 'success' : |
772 | 874 | this.filesPercents.setVisibility( false ); |
773 | 875 | this.syncPercents.display( { 'desc' : '', 'curr' : 'max' } ); |
774 | | - alert( this.formatMessage( 'synchronization_success' ) ); |
| 876 | + this.successHandler( this.formatMessage( 'synchronization_success' ) ); |
775 | 877 | // enable all buttons |
776 | | - this.setButtons( true ); |
777 | 878 | return; |
778 | 879 | } |
779 | 880 | }, |
— | — | @@ -843,9 +944,7 @@ |
844 | 945 | // binary search is complete |
845 | 946 | this.syncPercents.display( { 'desc' : '', 'curr' : 'max' } ); |
846 | 947 | if ( this.srcHiId === this.srcLastId && matches > 0 ) { |
847 | | - alert( this.formatMessage( 'already_synchronized' ) ); |
848 | | - // enable all buttons |
849 | | - this.setButtons( true ); |
| 948 | + this.successHandler( this.formatMessage( 'already_synchronized' ) ); |
850 | 949 | return; |
851 | 950 | } |
852 | 951 | this.log( 'Synchronizing from ' + this.srcHiId ); |
— | — | @@ -921,7 +1020,7 @@ |
922 | 1021 | this.srcLastId = this.srcHiId = parseInt( this.popAJAXresult( 'src_rev_last', ['query', 'revisionhistory', 0, 'revid'] ) ); |
923 | 1022 | // uncomment next line for "live" debugging |
924 | 1023 | // this.srcLastId = this.srcHiId = 75054; |
925 | | - this.syncPercents.display( { 'desc' : this.formatMessage( 'diff_search' ), 'curr' : 0, 'min' : 0, 'max' : Math.ceil( this.mathLogBase( this.srcLastId - this.srcFirstId, 2 ) ) } ); |
| 1024 | + this.syncPercents.display( { 'desc' : this.formatMessage( 'diff_search' ), 'curr' : 0, 'min' : 0, 'max' : Math.ceil( WikiSyncUtils.mathLogBase( this.srcLastId - this.srcFirstId, 2 ) ) } ); |
926 | 1025 | this.findCommonRev( { 'opcode' : 'start' } ); |
927 | 1026 | return; |
928 | 1027 | } |
— | — | @@ -946,6 +1045,13 @@ |
947 | 1046 | } |
948 | 1047 | // disable all buttons |
949 | 1048 | this.setButtons( false ); |
| 1049 | + WikiSyncScheduler.reset(); |
| 1050 | + this.schedulerLogger.log( |
| 1051 | + this.formatMessage( |
| 1052 | + this.directionToLocal ? 'sync_start_rtl' : 'sync_start_ltr', |
| 1053 | + WikiSyncUtils.getLocalDate() |
| 1054 | + ) |
| 1055 | + ); |
950 | 1056 | this.syncPercents.setVisibility( true ); |
951 | 1057 | /* get first and last source revision in parallel */ |
952 | 1058 | this.getSrcRev( { 'opcode' : 'start' } ); |
— | — | @@ -959,14 +1065,35 @@ |
960 | 1066 | return WikiSync.onloadHandler.call( WikiSync ); |
961 | 1067 | } |
962 | 1068 | // }}} |
| 1069 | + this.operationLogger = new WikiSyncLog( 'wikisync_remote_log' ); |
| 1070 | + this.schedulerLogger = new WikiSyncLog( 'wikisync_scheduler_log' ); |
963 | 1071 | this.syncPercents = new WikiSyncPercentsIndicator( 'wikisync_xml_percents' ); |
964 | 1072 | this.filesPercents = new WikiSyncPercentsIndicator( 'wikisync_files_percents' ); |
965 | 1073 | this.syncPercents.setVisibility( false ); |
966 | 1074 | this.filesPercents.setVisibility( false ); |
967 | 1075 | this.showIframe( '' ); |
968 | 1076 | this.errorDefaultAction(); |
| 1077 | + var loginForm = document.getElementById( 'remote_login_form' ); |
| 1078 | + // {{{ restore remote login / password cookies to login form, if any |
| 1079 | + WikiSyncUtils.cookieToInput( 'ruser', loginForm.remote_wiki_user ); |
| 1080 | + var rpass = WikiSyncUtils.cookieToInput( 'rpass', loginForm.remote_wiki_pass ); |
| 1081 | + // }}} |
| 1082 | + // {{{ restore scheduler cookies to scheduler form, if any |
| 1083 | + var schedulerForm = document.getElementById( 'scheduler_form' ); |
| 1084 | + WikiSyncUtils.cookieToCheckbox( 'auto_sync', schedulerForm.ws_auto_sync ); |
| 1085 | + WikiSyncUtils.cookieToCheckbox( 'auto_switch_direction', schedulerForm.ws_auto_switch_direction ); |
| 1086 | + WikiSyncUtils.cookieToInput( 'auto_sync_time_interval', schedulerForm.ws_auto_sync_time_interval ); |
| 1087 | + // }}} |
| 1088 | + if ( rpass !== null ) { |
| 1089 | + loginForm.ws_store_password.checked = true; |
| 1090 | + // try to autologin |
| 1091 | + this.submitRemoteLogin( loginForm ); |
| 1092 | + } |
| 1093 | + WikiSyncScheduler.setup( schedulerForm ); |
| 1094 | + window.setTimeout( function() { WikiSyncScheduler.poll(); }, this.pollTimeout ); |
969 | 1095 | } |
970 | 1096 | |
971 | 1097 | } |
972 | 1098 | |
973 | | -WikiSyncUtils.addEvent(window,"load", WikiSync.onloadHandler); |
| 1099 | +WikiSyncUtils.setCookiePrefix( wgDBname + '_wiki_WikiSync_' + WikiSync_md5.hex( wgUserName ) + '_' ); |
| 1100 | +WikiSyncUtils.addEvent( window, "load", WikiSync.onloadHandler ); |