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