]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/plugin/SyncWiki.php
Be more strict: link to merge conflicts (not yet handled)
[SourceForge/phpwiki.git] / lib / plugin / SyncWiki.php
1 <?php // -*-php-*-
2 rcs_id('$Id: SyncWiki.php,v 1.2 2007-01-20 11:24:46 rurban Exp $');
3 /**
4  Copyright 2006 $ThePhpWikiProgrammingTeam
5
6  This file is part of PhpWiki.
7
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.
12
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.
17
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
21  */
22
23 /**
24  * required argument:  url = <rpc interface to main wiki>
25  * optional arguments: noimport, noexport, noupload
26  *      
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?)
38  */
39 require_once("lib/loadsave.php");
40 include_once("lib/plugin/WikiAdminUtils.php");
41
42 class WikiPlugin_SyncWiki
43 extends WikiPlugin_WikiAdminUtils
44 {
45     function getName () {
46         return _("SyncWiki");
47     }
48
49     function getDescription () {
50         return _("Synchronize pages with external PhpWiki");
51     }
52
53     function getVersion() {
54         return preg_replace("/[Revision: $]/", '',
55                             "\$Revision: 1.2 $");
56     }
57
58     function getDefaultArguments() {
59         return array('url'    => '',
60                      'noimport' => 0,
61                      'noexport' => 0,
62                      'noupload' => 0,
63                      'label'  => $this->getName());
64     }
65
66     function run($dbi, $argstr, &$request, $basepage) {
67         $args = $this->getArgs($argstr, $request);
68         $args['action'] = 'syncwiki';
69         extract($args);
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);
77         }
78         return $this->_makeButton($request, $args, $label);
79     }
80
81     function _do_syncwiki(&$request, $args) {
82         global $charset;
83         longer_timeout(240);
84
85         if (!function_exists('wiki_xmlrpc_post')) {
86             include_once("lib/XmlRpcClient.php");
87         }
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);
96         }
97         $pagelist = wiki_xmlrpc_post('wiki.getRecentChanges', 
98                                      iso8601_encode($merge_point,1), $args['url']);
99         $html = HTML();
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.")));
106             echo "<br />\n";
107             PrintXML(fmt("Retrieving from external url %s wiki.getRecentChanges(%s)...", 
108                      $args['url'], iso8601_encode($merge_point,1)));
109             echo "<br />\n";
110             $ouriter = $dbh->mostRecent(array('since' => $merge_point));
111             //$ol = HTML::ol();
112             $done = array();
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);
120                 $done[$name] = 1;
121                 $ourrev  = $our->getCurrentRevision(false);
122                 $rel = '<=>';
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".")");
130                     } else {
131                         $reaction = $this->_import($args, $our, $extdate);
132                     }
133                 } else {
134                     $ourdate = $ourrev->get('mtime');
135                     if ($extdate > $ourdate and $ourdate < $merge_point) {
136                         $rel = '>';
137                         $reaction = $this->_import($args, $our, $extdate);
138                     } elseif ($extdate > $ourdate and $ourdate >= $merge_point) {
139                         $rel = '>';
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) {
143                         $rel = '>';
144                         $reaction = $this->_export($args, $our);
145                     } elseif ($extdate < $ourdate and $extdate >= $merge_point) {
146                         $rel = '>';
147                         // our is newer and external is also newer
148                         $reaction = $this->_addConflict('export', $args, $our, $extdate);
149                     } else {
150                         $rel = '==';
151                         $reaction = _("same date");
152                     }
153                 }
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),
160                          HTML::br());
161                 $request->chunkOutput();
162             }
163             //$html->pushContent($ol);
164         } else {
165             trigger_error("xmlrpc error:  wiki.getRecentChanges returned "
166                           ."(".gettype($pagelist).") ".$pagelist, E_USER_WARNING);
167             EndLoadDump($request);
168             return $html;
169         }
170
171         if (empty($args['noexport'])) {
172             PrintXML(HTML::strong(fmt("Now upload all locally newer pages.")));
173             echo "<br />\n";
174             PrintXML(fmt("Checking all local pages newer than %s...", 
175                      iso8601_encode($merge_point,1)));
176             echo "<br />\n";
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);
190                     }
191                 } else {
192                     $reaction = 'xmlrpc error';
193                 }
194                 PrintXML(HTML::strong($name)," ",
195                          $extdate," < ",$ourdate," ",
196                          HTML::strong($reaction),
197                          HTML::br());
198                 $request->chunkOutput();
199             }
200
201             PrintXML(HTML::strong(fmt("Now upload all locally newer uploads.")));
202             echo "<br />\n";
203             PrintXML(fmt("Checking all local uploads newer than %s...", 
204                      iso8601_encode($merge_point,1)));
205             echo "<br />\n";
206             $this->_fileList = array();
207             $prefix = getUploadFilePath();
208             $this->_dir($prefix);
209             $len = strlen($prefix);
210             foreach ($this->_fileList as $path) {
211                 // strip prefix
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);
223                     }
224                 } else {
225                     $reaction = 'xmlrpc error wiki.getUploadedFileInfo not supported';
226                 }
227                 PrintXML(HTML::strong($name)," ",
228                          "$extdate ($extsize) < $ourdate ($oursize)",
229                          HTML::strong($reaction),
230                          HTML::br());
231                 $request->chunkOutput();
232             }
233         }
234
235         $dbh->set('mergepoint', time());
236         EndLoadDump($request);
237         return ''; //$html;
238     }
239
240     /* path must have ending slash */
241     function _dir($path) {
242         $dh = @opendir($path);
243         while ($filename = readdir($dh)) {
244             if ($filename[0] == '.')
245                 continue;
246             $ft = filetype($path . $filename);
247             if ($ft == 'file')
248                 array_push($this->_fileList, $path . $filename);
249             else if ($ft == 'dir')
250                 $this->_dir($path . $filename . "/");
251         }
252         closedir($dh);
253     }
254
255     function _addConflict($what, $args, $our, $extdate = null) {
256         $pagename = $our->getName();
257         $meb = Button(array('action' => $args['action'],
258                             'merge'=> true,
259                             'source'=> $f),
260                       _("Merge Edit"),
261                       $args['pagename'],
262                       'wikiadmin');
263         $owb = Button(array('action' => $args['action'],
264                             'overwrite'=> true,
265                             'source'=> $f),
266                       sprintf(_("%s force"), strtoupper(substr($what, 0, 1)).substr($what, 1)),
267                       $args['pagename'],
268                       'wikiunsafe');
269         $this->_conflicts[] = $pagename;
270         return HTML(fmt(_("Postponed %s for %s."), $what, $pagename), " ", $meb, " ", $owb);
271     }
272
273     // TODO: store log or checkpoint for restauration?
274     function _import($args, $our, $extdate = null) {
275         global $request;
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))
288                 $extdate = time();      
289             $our->save(utf8_decode($pagedata), -1, array('author' => $userid,
290                                                          'mtime' => $extdate));
291             $reaction .= _("OK");
292         } else 
293               $reaction .= (_("FAILED").' ('.gettype($pagedata).')');
294         return $reaction;
295     }
296
297     // TODO: store log or checkpoint for restauration?
298     function _export($args, $our) {
299         global $request;
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");
311         }
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']);
318             else
319                 $reaction .= (_("FAILED").' '.$success['code']." ".$success['message']);
320         } else 
321             $reaction .= (_("FAILED"));
322         return $reaction;
323     }
324
325     // TODO: store log or checkpoint for restauration?
326     function _upload($args, $path, $timeout) {
327         global $request;
328         $reaction = 'upload ';
329         if ($args['noupload']) return ($reaction._("skipped"));
330
331         //$userid  = $request->_user->_userid;
332         $url = $args['url'];
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);
338         if ($success) {
339             if ($http->getStatus() == 200)
340                 $reaction .= _("OK");
341             else
342                 $reaction .= (_("FAILED").' '.$http->getStatus());
343         } else
344             $reaction .= (_("FAILED").' '.$http->getStatus()." ".$http->errormsg);
345         return $reaction;
346     }
347 };
348
349 // $Log: not supported by cvs2svn $
350 // Revision 1.1  2007/01/02 13:22:57  rurban
351 // add SyncWiki
352 //
353
354 // For emacs users
355 // Local Variables:
356 // mode: php
357 // tab-width: 8
358 // c-basic-offset: 4
359 // c-hanging-comment-ender-p: nil
360 // indent-tabs-mode: nil
361 // End:
362 ?>