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