2 rcs_id('$Id: SyncWiki.php,v 1.2 2007-01-20 11:24:46 rurban Exp $');
4 Copyright 2006 $ThePhpWikiProgrammingTeam
6 This file is part of PhpWiki.
8 PhpWiki is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
13 PhpWiki is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with PhpWiki; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 * required argument: url = <rpc interface to main wiki>
25 * optional arguments: noimport, noexport, noupload
27 * 1. check RPC2 interface or admin url (lang?) of external wiki
28 * get external pagelist, only later than our last mergepoint
29 * 2. Download all externally changed sources:
30 * If local page is older than the mergepoint, import it.
31 * If local page does not exist (deleted?), and there is no revision, import it.
32 * Else we deleted it. Skip the import, but don't delete the external. Should be added to conflict.
33 * If local page is newer than the mergepoint, then add it to the conflict pages.
34 * 3. check our to_delete, to_add, to_merge
35 * 4. get our pagelist of pages only later than our last mergepoint
36 * 5. check external to_delete, to_add, to_merge
37 * 6. store log (where, how?)
39 require_once("lib/loadsave.php");
40 include_once("lib/plugin/WikiAdminUtils.php");
42 class WikiPlugin_SyncWiki
43 extends WikiPlugin_WikiAdminUtils
49 function getDescription () {
50 return _("Synchronize pages with external PhpWiki");
53 function getVersion() {
54 return preg_replace("/[Revision: $]/", '',
58 function getDefaultArguments() {
59 return array('url' => '',
63 'label' => $this->getName());
66 function run($dbi, $argstr, &$request, $basepage) {
67 $args = $this->getArgs($argstr, $request);
68 $args['action'] = 'syncwiki';
70 if (empty($args['url']))
71 return $this->error(fmt("A required argument '%s' is missing.", "url"));
72 if ($request->getArg('action') != 'browse')
73 return $this->disabled("(action != 'browse')");
74 $posted = $request->getArg('wikiadminutils');
75 if ($request->isPost() and $posted['action'] == $action) {
76 return $this->_do_syncwiki($request, $posted);
78 return $this->_makeButton($request, $args, $label);
81 function _do_syncwiki(&$request, $args) {
85 if (!function_exists('wiki_xmlrpc_post')) {
86 include_once("lib/XmlRpcClient.php");
88 $userid = $request->_user->_userid;
89 $dbh = $request->getDbh();
90 $merge_point = $dbh->get('mergepoint');
91 if (empty($merge_point)) {
92 $page = $dbh->getPage("ReleaseNotes"); // this is usually the latest official page
93 $last = $page->getCurrentRevision(false);
94 $merge_point = $last->get("mtime"); // for testing: 1160396075
95 $dbh->set('mergepoint', $merge_point);
97 $pagelist = wiki_xmlrpc_post('wiki.getRecentChanges',
98 iso8601_encode($merge_point,1), $args['url']);
100 //$html->pushContent(HTML::div(HTML::em("check RPC2 interface...")));
101 if (gettype($pagelist) === "array") {
102 //$request->_deferredPageChangeNotification = array();
103 $request->discardOutput();
104 StartLoadDump($request, _("Syncing this PhpWiki"));
105 PrintXML(HTML::strong(fmt("Download all externally changed sources.")));
107 PrintXML(fmt("Retrieving from external url %s wiki.getRecentChanges(%s)...",
108 $args['url'], iso8601_encode($merge_point,1)));
110 $ouriter = $dbh->mostRecent(array('since' => $merge_point));
113 foreach ($pagelist as $ext) {
114 $reaction = _("<unknown>");
115 // compare existance and dates with local page
116 $extdate = iso8601_decode($ext['lastModified']->scalar,1);
117 // TODO: urldecode ???
118 $name = utf8_decode($ext['name']);
119 $our = $dbh->getPage($name);
121 $ourrev = $our->getCurrentRevision(false);
123 if (!$our->exists()) {
124 // we might have deleted or moved it on purpose?
125 // check date of latest revision if there's one, and > mergepoint
126 if (($ourrev->getVersion() > 1) and ($ourrev->get('mtime') > $merge_point)) {
127 // our was deleted after sync, and changed after last sync.
128 $this->_addConflict('delete', $args, $our, $extdate);
129 $reaction = (_(" skipped")." ("."locally deleted or moved".")");
131 $reaction = $this->_import($args, $our, $extdate);
134 $ourdate = $ourrev->get('mtime');
135 if ($extdate > $ourdate and $ourdate < $merge_point) {
137 $reaction = $this->_import($args, $our, $extdate);
138 } elseif ($extdate > $ourdate and $ourdate >= $merge_point) {
140 // our is older then external but newer than last sync
141 $reaction = $this->_addConflict('import', $args, $our, $extdate);
142 } elseif ($extdate < $ourdate and $extdate < $merge_point) {
144 $reaction = $this->_export($args, $our);
145 } elseif ($extdate < $ourdate and $extdate >= $merge_point) {
147 // our is newer and external is also newer
148 $reaction = $this->_addConflict('export', $args, $our, $extdate);
151 $reaction = _("same date");
154 /*$ol->pushContent(HTML::li(HTML::strong($name)," ",
155 $extdate,"<=>",$ourdate," ",
156 HTML::strong($reaction))); */
157 PrintXML(HTML::strong($name)," ",
158 $extdate," $rel ",$ourdate," ",
159 HTML::strong($reaction),
161 $request->chunkOutput();
163 //$html->pushContent($ol);
165 trigger_error("xmlrpc error: wiki.getRecentChanges returned "
166 ."(".gettype($pagelist).") ".$pagelist, E_USER_WARNING);
167 EndLoadDump($request);
171 if (empty($args['noexport'])) {
172 PrintXML(HTML::strong(fmt("Now upload all locally newer pages.")));
174 PrintXML(fmt("Checking all local pages newer than %s...",
175 iso8601_encode($merge_point,1)));
177 while ($our = $ouriter->next()) {
178 $name = $our->getName();
179 if ($done[$name]) continue;
180 $reaction = _(" skipped");
181 $ext = wiki_xmlrpc_post('wiki.getPageInfo', $name, $args['url']);
182 if (is_array($ext)) {
183 $extdate = iso8601_decode($ext['lastModified']->scalar,1);
184 $ourdate = $our->get('mtime');
185 if ($extdate < $ourdate and $extdate < $merge_point) {
186 $reaction = $this->_export($args, $our);
187 } elseif ($extdate < $ourdate and $extdate >= $merge_point) {
188 // our newer and external newer
189 $reaction = $this->_addConflict($args, $our, $extdate);
192 $reaction = 'xmlrpc error';
194 PrintXML(HTML::strong($name)," ",
195 $extdate," < ",$ourdate," ",
196 HTML::strong($reaction),
198 $request->chunkOutput();
201 PrintXML(HTML::strong(fmt("Now upload all locally newer uploads.")));
203 PrintXML(fmt("Checking all local uploads newer than %s...",
204 iso8601_encode($merge_point,1)));
206 $this->_fileList = array();
207 $prefix = getUploadFilePath();
208 $this->_dir($prefix);
209 $len = strlen($prefix);
210 foreach ($this->_fileList as $path) {
212 $file = substr($path,$len);
213 $ourdate = filemtime($path);
214 $oursize = filesize($path);
215 $reaction = _(" skipped");
216 $ext = wiki_xmlrpc_post('wiki.getUploadedFileInfo', $file, $args['url']);
217 if (is_array($ext)) {
218 $extdate = iso8601_decode($ext['lastModified']->scalar,1);
219 $extsize = $ext['size'];
220 if (empty($extsize) or $extdate < $ourdate) {
221 $timeout = $oursize * 0.0002; // assume 50kb/sec upload speed
222 $reaction = $this->_upload($args, $path, $timeout);
225 $reaction = 'xmlrpc error wiki.getUploadedFileInfo not supported';
227 PrintXML(HTML::strong($name)," ",
228 "$extdate ($extsize) < $ourdate ($oursize)",
229 HTML::strong($reaction),
231 $request->chunkOutput();
235 $dbh->set('mergepoint', time());
236 EndLoadDump($request);
240 /* path must have ending slash */
241 function _dir($path) {
242 $dh = @opendir($path);
243 while ($filename = readdir($dh)) {
244 if ($filename[0] == '.')
246 $ft = filetype($path . $filename);
248 array_push($this->_fileList, $path . $filename);
249 else if ($ft == 'dir')
250 $this->_dir($path . $filename . "/");
255 function _addConflict($what, $args, $our, $extdate = null) {
256 $pagename = $our->getName();
257 $meb = Button(array('action' => $args['action'],
263 $owb = Button(array('action' => $args['action'],
266 sprintf(_("%s force"), strtoupper(substr($what, 0, 1)).substr($what, 1)),
269 $this->_conflicts[] = $pagename;
270 return HTML(fmt(_("Postponed %s for %s."), $what, $pagename), " ", $meb, " ", $owb);
273 // TODO: store log or checkpoint for restauration?
274 function _import($args, $our, $extdate = null) {
276 $reaction = 'import ';
277 if ($args['noimport']) return ($reaction._("skipped"));
278 $userid = $request->_user->_userid;
279 $name = $our->getName();
280 $pagedata = wiki_xmlrpc_post('wiki.getPage', $name, $args['url']);
281 if (is_object($pagedata)) {
282 $pagedata = $pagedata->scalar;
283 $ourrev = $our->getCurrentRevision(true);
284 $content = $ourrev->getPackedContent();
285 if ($pagedata == $content)
286 return $reaction . _("skipped").' '._("same content");
287 if (is_null($extdate))
289 $our->save(utf8_decode($pagedata), -1, array('author' => $userid,
290 'mtime' => $extdate));
291 $reaction .= _("OK");
293 $reaction .= (_("FAILED").' ('.gettype($pagedata).')');
297 // TODO: store log or checkpoint for restauration?
298 function _export($args, $our) {
300 $reaction = 'export ';
301 if ($args['noexport']) return ($reaction._("skipped"));
302 $userid = $request->_user->_userid;
303 $name = $our->getName();
304 $ourrev = $our->getCurrentRevision(true);
305 $content = $ourrev->getPackedContent();
306 $extdata = wiki_xmlrpc_post('wiki.getPage', $name, $args['url']);
307 if (is_object($extdata)) {
308 $extdata = $extdata->scalar;
309 if ($extdata == $content)
310 return $reaction . _("skipped").' '._("same content");
312 $mypass = $request->getPref('passwd'); // this usually fails
313 $success = wiki_xmlrpc_post('wiki.putPage',
314 array($name, $content, $userid, $mypass), $args['url']);
315 if (is_array($success)) {
316 if ($success['code'] == 200)
317 $reaction .= (_("OK").' '.$success['code']." ".$success['message']);
319 $reaction .= (_("FAILED").' '.$success['code']." ".$success['message']);
321 $reaction .= (_("FAILED"));
325 // TODO: store log or checkpoint for restauration?
326 function _upload($args, $path, $timeout) {
328 $reaction = 'upload ';
329 if ($args['noupload']) return ($reaction._("skipped"));
331 //$userid = $request->_user->_userid;
333 $url = str_replace("/RPC2.php","/index.php", $url);
334 $server = parse_url($url);
335 $http = new HttpClient($server['host'], $server['port']);
336 $http->timeout = $timeout + 5;
337 $success = $http->postfile($server['url'], $path);
339 if ($http->getStatus() == 200)
340 $reaction .= _("OK");
342 $reaction .= (_("FAILED").' '.$http->getStatus());
344 $reaction .= (_("FAILED").' '.$http->getStatus()." ".$http->errormsg);
349 // $Log: not supported by cvs2svn $
350 // Revision 1.1 2007/01/02 13:22:57 rurban
359 // c-hanging-comment-ender-p: nil
360 // indent-tabs-mode: nil