]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/plugin/SyncWiki.php
Replace tabs by spaces; remove EOL spaces
[SourceForge/phpwiki.git] / lib / plugin / SyncWiki.php
1 <?php // -*-php-*-
2 rcs_id('$Id$');
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$");
56     }
57
58     function getDefaultArguments() {
59         return array('url'    => '',
60                      'noimport' => 0,
61                      'noexport' => 0,
62                      'noupload' => 0,
63                      'label'  => $this->getName(),
64                      //'userid' => false,
65                      'passwd' => false,
66                      'sid'    => false,
67                      );
68     }
69
70     function run($dbi, $argstr, &$request, $basepage) {
71         $args = $this->getArgs($argstr, $request);
72         $args['action'] = 'syncwiki';
73         extract($args);
74         if (empty($args['url']))
75             return $this->error(fmt("A required argument '%s' is missing.", "url"));
76         if ($request->getArg('action') != 'browse')
77             return $this->disabled("(action != 'browse')");
78         $posted = $request->getArg('wikiadminutils');
79         if ($request->isPost()
80             and $posted['action'] == $action
81             and $posted['url'] == $url) // multiple buttons
82         {
83             return $this->_do_syncwiki($request, $posted);
84         }
85         return $this->_makeButton($request, $args, $label);
86     }
87
88     function _do_syncwiki(&$request, $args) {
89         global $charset;
90         longer_timeout(240);
91
92         if (!function_exists('wiki_xmlrpc_post')) {
93             include_once("lib/XmlRpcClient.php");
94         }
95         $userid = $request->_user->_userid;
96         $dbh = $request->getDbh();
97         $merge_point = $dbh->get('mergepoint');
98         if (empty($merge_point)) {
99             $page = $dbh->getPage("ReleaseNotes"); // this is usually the latest official page
100             $last = $page->getCurrentRevision(false);
101             $merge_point = $last->get("mtime");    // for testing: 1160396075
102             $dbh->set('mergepoint', $merge_point);
103         }
104         //TODO: remote auth, set session cookie
105         $pagelist = wiki_xmlrpc_post('wiki.getRecentChanges',
106                                      iso8601_encode($merge_point,1),
107                                      $args['url'], $args);
108         $html = HTML();
109         //$html->pushContent(HTML::div(HTML::em("check RPC2 interface...")));
110         if (gettype($pagelist) === "array") {
111             //$request->_deferredPageChangeNotification = array();
112             $request->discardOutput();
113             StartLoadDump($request, _("Syncing this PhpWiki"));
114             PrintXML(HTML::strong(fmt("Download all externally changed sources.")));
115             echo "<br />\n";
116             PrintXML(fmt("Retrieving from external url %s wiki.getRecentChanges(%s)...",
117                      $args['url'], iso8601_encode($merge_point,1)));
118             echo "<br />\n";
119             $ouriter = $dbh->mostRecent(array('since' => $merge_point));
120             //$ol = HTML::ol();
121             $done = array();
122             foreach ($pagelist as $ext) {
123                 $reaction = _("<unknown>");
124                 // compare existance and dates with local page
125                 $extdate = iso8601_decode($ext['lastModified']->scalar,1);
126                 // TODO: urldecode ???
127                 $name = utf8_decode($ext['name']);
128                 $our = $dbh->getPage($name);
129                 $done[$name] = 1;
130                 $ourrev  = $our->getCurrentRevision(false);
131                 $rel = '<=>';
132                 if (!$our->exists()) {
133                     // we might have deleted or moved it on purpose?
134                     // check date of latest revision if there's one, and > mergepoint
135                     if (($ourrev->getVersion() > 1) and ($ourrev->get('mtime') > $merge_point)) {
136                         // our was deleted after sync, and changed after last sync.
137                         $this->_addConflict('delete', $args, $our, $extdate);
138                         $reaction = (_(" skipped")." ("."locally deleted or moved".")");
139                     } else {
140                         $reaction = $this->_import($args, $our, $extdate);
141                     }
142                 } else {
143                     $ourdate = $ourrev->get('mtime');
144                     if ($extdate > $ourdate and $ourdate < $merge_point) {
145                             $rel = '>';
146                         $reaction = $this->_import($args, $our, $extdate);
147                     } elseif ($extdate > $ourdate and $ourdate >= $merge_point) {
148                             $rel = '>';
149                         // our is older then external but newer than last sync
150                         $reaction = $this->_addConflict('import', $args, $our, $extdate);
151                     } elseif ($extdate < $ourdate and $extdate < $merge_point) {
152                             $rel = '>';
153                         $reaction = $this->_export($args, $our);
154                     } elseif ($extdate < $ourdate and $extdate >= $merge_point) {
155                             $rel = '>';
156                         // our is newer and external is also newer
157                         $reaction = $this->_addConflict('export', $args, $our, $extdate);
158                     } else {
159                             $rel = '==';
160                         $reaction = _("same date");
161                     }
162                 }
163                 /*$ol->pushContent(HTML::li(HTML::strong($name)," ",
164                                           $extdate,"<=>",$ourdate," ",
165                                           HTML::strong($reaction))); */
166                 PrintXML(HTML::strong($name)," ",
167                          $extdate," $rel ",$ourdate," ",
168                          HTML::strong($reaction),
169                          HTML::br());
170                 $request->chunkOutput();
171             }
172             //$html->pushContent($ol);
173         } else {
174             $html->pushContent("xmlrpc error:  wiki.getRecentChanges returned "
175                           ."(".gettype($pagelist).") ".$pagelist);
176             trigger_error("xmlrpc error:  wiki.getRecentChanges returned "
177                           ."(".gettype($pagelist).") ".$pagelist, E_USER_WARNING);
178             EndLoadDump($request);
179             return $this->error($html);
180         }
181
182         if (empty($args['noexport'])) {
183             PrintXML(HTML::strong(fmt("Now upload all locally newer pages.")));
184             echo "<br />\n";
185             PrintXML(fmt("Checking all local pages newer than %s...",
186                      iso8601_encode($merge_point,1)));
187             echo "<br />\n";
188             while ($our = $ouriter->next()) {
189                 $name = $our->getName();
190                 if ($done[$name]) continue;
191                 $reaction = _(" skipped");
192                 $ext = wiki_xmlrpc_post('wiki.getPageInfo', $name, $args['url']);
193                 if (is_array($ext)) {
194                     $extdate = iso8601_decode($ext['lastModified']->scalar,1);
195                     $ourdate = $our->get('mtime');
196                     if ($extdate < $ourdate and $extdate < $merge_point) {
197                         $reaction = $this->_export($args, $our);
198                     } elseif ($extdate < $ourdate and $extdate >= $merge_point) {
199                         // our newer and external newer
200                         $reaction = $this->_addConflict($args, $our, $extdate);
201                     }
202                 } else {
203                     $reaction = 'xmlrpc error';
204                 }
205                 PrintXML(HTML::strong($name)," ",
206                          $extdate," < ",$ourdate," ",
207                          HTML::strong($reaction),
208                          HTML::br());
209                 $request->chunkOutput();
210             }
211
212             PrintXML(HTML::strong(fmt("Now upload all locally newer uploads.")));
213             echo "<br />\n";
214             PrintXML(fmt("Checking all local uploads newer than %s...",
215                      iso8601_encode($merge_point,1)));
216             echo "<br />\n";
217             $this->_fileList = array();
218             $prefix = getUploadFilePath();
219             $this->_dir($prefix);
220             $len = strlen($prefix);
221             foreach ($this->_fileList as $path) {
222                 // strip prefix
223                 $file = substr($path,$len);
224                 $ourdate = filemtime($path);
225                 $oursize = filesize($path);
226                 $reaction = _(" skipped");
227                 $ext = wiki_xmlrpc_post('wiki.getUploadedFileInfo', $file, $args['url']);
228                 if (is_array($ext)) {
229                     $extdate = iso8601_decode($ext['lastModified']->scalar,1);
230                     $extsize = $ext['size'];
231                     if (empty($extsize) or $extdate < $ourdate) {
232                         $timeout = $oursize * 0.0002;  // assume 50kb/sec upload speed
233                         $reaction = $this->_upload($args, $path, $timeout);
234                     }
235                 } else {
236                     $reaction = 'xmlrpc error wiki.getUploadedFileInfo not supported';
237                 }
238                 PrintXML(HTML::strong($name)," ",
239                          "$extdate ($extsize) < $ourdate ($oursize)",
240                          HTML::strong($reaction),
241                          HTML::br());
242                 $request->chunkOutput();
243             }
244         }
245
246         $dbh->set('mergepoint', time());
247         EndLoadDump($request);
248         return ''; //$html;
249     }
250
251     /* path must have ending slash */
252     function _dir($path) {
253         $dh = @opendir($path);
254         while ($filename = readdir($dh)) {
255             if ($filename[0] == '.')
256                 continue;
257             $ft = filetype($path . $filename);
258             if ($ft == 'file')
259                 array_push($this->_fileList, $path . $filename);
260             else if ($ft == 'dir')
261                 $this->_dir($path . $filename . "/");
262         }
263         closedir($dh);
264     }
265
266     function _addConflict($what, $args, $our, $extdate = null) {
267         $pagename = $our->getName();
268         $meb = Button(array('action' => $args['action'],
269                             'merge'=> true,
270                             'source'=> $f),
271                       _("Merge Edit"),
272                       $args['pagename'],
273                       'wikiadmin');
274         $owb = Button(array('action' => $args['action'],
275                             'overwrite'=> true,
276                             'source'=> $f),
277                       sprintf(_("%s force"), strtoupper(substr($what, 0, 1)).substr($what, 1)),
278                       $args['pagename'],
279                       'wikiunsafe');
280         $this->_conflicts[] = $pagename;
281         return HTML(fmt(_("Postponed %s for %s."), $what, $pagename), " ", $meb, " ", $owb);
282     }
283
284     // TODO: store log or checkpoint for restauration?
285     function _import($args, $our, $extdate = null) {
286         global $request;
287         $reaction = 'import ';
288         if ($args['noimport']) return ($reaction._("skipped"));
289         //$userid = $request->_user->_userid;
290         $name = $our->getName();
291         $pagedata = wiki_xmlrpc_post('wiki.getPage', $name, $args['url']);
292         if (is_object($pagedata)) {
293             $pagedata = $pagedata->scalar;
294             $ourrev  = $our->getCurrentRevision(true);
295             $content = $ourrev->getPackedContent();
296             if ($pagedata == $content)
297                     return $reaction . _("skipped").' '._("same content");
298             if (is_null($extdate))
299                 $extdate = time();
300             $our->save(utf8_decode($pagedata), -1, array('author' => $userid,
301                                                              'mtime' => $extdate));
302             $reaction .= _("OK");
303         } else
304               $reaction .= (_("FAILED").' ('.gettype($pagedata).')');
305         return $reaction;
306     }
307
308     // TODO: store log or checkpoint for restauration?
309     function _export($args, $our) {
310         global $request;
311         $reaction = 'export ';
312         if ($args['noexport']) return ($reaction._("skipped"));
313         $userid  = $request->_user->_userid;
314         $name    = $our->getName();
315         $ourrev  = $our->getCurrentRevision(true);
316         $content = $ourrev->getPackedContent();
317         $extdata = wiki_xmlrpc_post('wiki.getPage', $name, $args['url']);
318         if (is_object($extdata)) {
319             $extdata = $extdata->scalar;
320             if ($extdata == $content)
321                     return $reaction . _("skipped").' '._("same content");
322         }
323         $mypass  = $request->getPref('passwd'); // this usually fails
324         $success = wiki_xmlrpc_post('wiki.putPage',
325                                     array($name, $content, $userid, $mypass), $args['url']);
326         if (is_array($success)) {
327             if ($success['code'] == 200)
328                 $reaction .= (_("OK").' '.$success['code']." ".$success['message']);
329             else
330                 $reaction .= (_("FAILED").' '.$success['code']." ".$success['message']);
331         } else
332             $reaction .= (_("FAILED"));
333         return $reaction;
334     }
335
336     // TODO: store log or checkpoint for restauration?
337     function _upload($args, $path, $timeout) {
338         global $request;
339         $reaction = 'upload ';
340         if ($args['noupload']) return ($reaction._("skipped"));
341
342         //$userid  = $request->_user->_userid;
343         $url = $args['url'];
344         $url = str_replace("/RPC2.php","/index.php", $url);
345         $server = parse_url($url);
346         $http = new HttpClient($server['host'], $server['port']);
347         $http->timeout = $timeout + 5;
348         $success = $http->postfile($server['url'], $path);
349         if ($success) {
350             if ($http->getStatus() == 200)
351                 $reaction .= _("OK");
352             else
353                 $reaction .= (_("FAILED").' '.$http->getStatus());
354         } else
355             $reaction .= (_("FAILED").' '.$http->getStatus()." ".$http->errormsg);
356         return $reaction;
357     }
358 };
359
360 // For emacs users
361 // Local Variables:
362 // mode: php
363 // tab-width: 8
364 // c-basic-offset: 4
365 // c-hanging-comment-ender-p: nil
366 // indent-tabs-mode: nil
367 // End:
368 ?>