* optional arguments: noimport, noexport, noupload * * 1. check RPC2 interface or admin url (lang?) of external wiki * get external pagelist, only later than our last mergepoint * 2. Download all externally changed sources: * If local page is older than the mergepoint, import it. * If local page does not exist (deleted?), and there is no revision, import it. * Else we deleted it. Skip the import, but don't delete the external. Should be added to conflict. * If local page is newer than the mergepoint, then add it to the conflict pages. * 3. check our to_delete, to_add, to_merge * 4. get our pagelist of pages only later than our last mergepoint * 5. check external to_delete, to_add, to_merge * 6. store log (where, how?) */ require_once("lib/loadsave.php"); include_once("lib/plugin/WikiAdminUtils.php"); class WikiPlugin_SyncWiki extends WikiPlugin_WikiAdminUtils { function getName () { return _("SyncWiki"); } function getDescription () { return _("Synchronize pages with external PhpWiki"); } function getDefaultArguments() { return array('url' => '', 'noimport' => 0, 'noexport' => 0, 'noupload' => 0, 'label' => $this->getName(), //'userid' => false, 'passwd' => false, 'sid' => false, ); } function run($dbi, $argstr, &$request, $basepage) { $args = $this->getArgs($argstr, $request); $args['action'] = 'syncwiki'; extract($args); if (empty($args['url'])) return $this->error(fmt("A required argument '%s' is missing.", "url")); if ($request->getArg('action') != 'browse') return $this->disabled("(action != 'browse')"); $posted = $request->getArg('wikiadminutils'); if ($request->isPost() and $posted['action'] == $action and $posted['url'] == $url) // multiple buttons { return $this->_do_syncwiki($request, $posted); } return $this->_makeButton($request, $args, $label); } function _do_syncwiki(&$request, $args) { global $charset; longer_timeout(240); if (!function_exists('wiki_xmlrpc_post')) { include_once("lib/XmlRpcClient.php"); } $userid = $request->_user->_userid; $dbh = $request->getDbh(); $merge_point = $dbh->get('mergepoint'); if (empty($merge_point)) { $page = $dbh->getPage("ReleaseNotes"); // this is usually the latest official page $last = $page->getCurrentRevision(false); $merge_point = $last->get("mtime"); // for testing: 1160396075 $dbh->set('mergepoint', $merge_point); } //TODO: remote auth, set session cookie $pagelist = wiki_xmlrpc_post('wiki.getRecentChanges', iso8601_encode($merge_point,1), $args['url'], $args); $html = HTML(); //$html->pushContent(HTML::div(HTML::em("check RPC2 interface..."))); if (gettype($pagelist) === "array") { //$request->_deferredPageChangeNotification = array(); $request->discardOutput(); StartLoadDump($request, _("Syncing this PhpWiki")); PrintXML(HTML::strong(fmt("Download all externally changed sources."))); echo "
\n"; PrintXML(fmt("Retrieving from external url %s wiki.getRecentChanges(%s)...", $args['url'], iso8601_encode($merge_point,1))); echo "
\n"; $ouriter = $dbh->mostRecent(array('since' => $merge_point)); //$ol = HTML::ol(); $done = array(); foreach ($pagelist as $ext) { $reaction = _(""); // compare existance and dates with local page $extdate = iso8601_decode($ext['lastModified']->scalar,1); // TODO: urldecode ??? $name = utf8_decode($ext['name']); $our = $dbh->getPage($name); $done[$name] = 1; $ourrev = $our->getCurrentRevision(false); $rel = '<=>'; if (!$our->exists()) { // we might have deleted or moved it on purpose? // check date of latest revision if there's one, and > mergepoint if (($ourrev->getVersion() > 1) and ($ourrev->get('mtime') > $merge_point)) { // our was deleted after sync, and changed after last sync. $this->_addConflict('delete', $args, $our, $extdate); $reaction = (_(" skipped")." ("."locally deleted or moved".")"); } else { $reaction = $this->_import($args, $our, $extdate); } } else { $ourdate = $ourrev->get('mtime'); if ($extdate > $ourdate and $ourdate < $merge_point) { $rel = '>'; $reaction = $this->_import($args, $our, $extdate); } elseif ($extdate > $ourdate and $ourdate >= $merge_point) { $rel = '>'; // our is older then external but newer than last sync $reaction = $this->_addConflict('import', $args, $our, $extdate); } elseif ($extdate < $ourdate and $extdate < $merge_point) { $rel = '>'; $reaction = $this->_export($args, $our); } elseif ($extdate < $ourdate and $extdate >= $merge_point) { $rel = '>'; // our is newer and external is also newer $reaction = $this->_addConflict('export', $args, $our, $extdate); } else { $rel = '=='; $reaction = _("same date"); } } /*$ol->pushContent(HTML::li(HTML::strong($name)," ", $extdate,"<=>",$ourdate," ", HTML::strong($reaction))); */ PrintXML(HTML::strong($name)," ", $extdate," $rel ",$ourdate," ", HTML::strong($reaction), HTML::br()); $request->chunkOutput(); } //$html->pushContent($ol); } else { $html->pushContent("xmlrpc error: wiki.getRecentChanges returned " ."(".gettype($pagelist).") ".$pagelist); trigger_error("xmlrpc error: wiki.getRecentChanges returned " ."(".gettype($pagelist).") ".$pagelist, E_USER_WARNING); EndLoadDump($request); return $this->error($html); } if (empty($args['noexport'])) { PrintXML(HTML::strong(fmt("Now upload all locally newer pages."))); echo "
\n"; PrintXML(fmt("Checking all local pages newer than %s...", iso8601_encode($merge_point,1))); echo "
\n"; while ($our = $ouriter->next()) { $name = $our->getName(); if ($done[$name]) continue; $reaction = _(" skipped"); $ext = wiki_xmlrpc_post('wiki.getPageInfo', $name, $args['url']); if (is_array($ext)) { $extdate = iso8601_decode($ext['lastModified']->scalar,1); $ourdate = $our->get('mtime'); if ($extdate < $ourdate and $extdate < $merge_point) { $reaction = $this->_export($args, $our); } elseif ($extdate < $ourdate and $extdate >= $merge_point) { // our newer and external newer $reaction = $this->_addConflict($args, $our, $extdate); } } else { $reaction = 'xmlrpc error'; } PrintXML(HTML::strong($name)," ", $extdate," < ",$ourdate," ", HTML::strong($reaction), HTML::br()); $request->chunkOutput(); } PrintXML(HTML::strong(fmt("Now upload all locally newer uploads."))); echo "
\n"; PrintXML(fmt("Checking all local uploads newer than %s...", iso8601_encode($merge_point,1))); echo "
\n"; $this->_fileList = array(); $prefix = getUploadFilePath(); $this->_dir($prefix); $len = strlen($prefix); foreach ($this->_fileList as $path) { // strip prefix $file = substr($path,$len); $ourdate = filemtime($path); $oursize = filesize($path); $reaction = _(" skipped"); $ext = wiki_xmlrpc_post('wiki.getUploadedFileInfo', $file, $args['url']); if (is_array($ext)) { $extdate = iso8601_decode($ext['lastModified']->scalar,1); $extsize = $ext['size']; if (empty($extsize) or $extdate < $ourdate) { $timeout = $oursize * 0.0002; // assume 50kb/sec upload speed $reaction = $this->_upload($args, $path, $timeout); } } else { $reaction = 'xmlrpc error wiki.getUploadedFileInfo not supported'; } PrintXML(HTML::strong($name)," ", "$extdate ($extsize) < $ourdate ($oursize)", HTML::strong($reaction), HTML::br()); $request->chunkOutput(); } } $dbh->set('mergepoint', time()); EndLoadDump($request); return ''; //$html; } /* path must have ending slash */ function _dir($path) { $dh = @opendir($path); while ($filename = readdir($dh)) { if ($filename[0] == '.') continue; $ft = filetype($path . $filename); if ($ft == 'file') array_push($this->_fileList, $path . $filename); else if ($ft == 'dir') $this->_dir($path . $filename . "/"); } closedir($dh); } function _addConflict($what, $args, $our, $extdate = null) { $pagename = $our->getName(); $meb = Button(array('action' => $args['action'], 'merge'=> true, 'source'=> $f), _("Merge Edit"), $args['pagename'], 'wikiadmin'); $owb = Button(array('action' => $args['action'], 'overwrite'=> true, 'source'=> $f), sprintf(_("%s force"), strtoupper(substr($what, 0, 1)).substr($what, 1)), $args['pagename'], 'wikiunsafe'); $this->_conflicts[] = $pagename; return HTML(fmt(_("Postponed %s for %s."), $what, $pagename), " ", $meb, " ", $owb); } // TODO: store log or checkpoint for restauration? function _import($args, $our, $extdate = null) { global $request; $reaction = 'import '; if ($args['noimport']) return ($reaction._("skipped")); //$userid = $request->_user->_userid; $name = $our->getName(); $pagedata = wiki_xmlrpc_post('wiki.getPage', $name, $args['url']); if (is_object($pagedata)) { $pagedata = $pagedata->scalar; $ourrev = $our->getCurrentRevision(true); $content = $ourrev->getPackedContent(); if ($pagedata == $content) return $reaction . _("skipped").' '._("same content"); if (is_null($extdate)) $extdate = time(); $our->save(utf8_decode($pagedata), -1, array('author' => $userid, 'mtime' => $extdate)); $reaction .= _("OK"); } else $reaction .= (_("FAILED").' ('.gettype($pagedata).')'); return $reaction; } // TODO: store log or checkpoint for restauration? function _export($args, $our) { global $request; $reaction = 'export '; if ($args['noexport']) return ($reaction._("skipped")); $userid = $request->_user->_userid; $name = $our->getName(); $ourrev = $our->getCurrentRevision(true); $content = $ourrev->getPackedContent(); $extdata = wiki_xmlrpc_post('wiki.getPage', $name, $args['url']); if (is_object($extdata)) { $extdata = $extdata->scalar; if ($extdata == $content) return $reaction . _("skipped").' '._("same content"); } $mypass = $request->getPref('passwd'); // this usually fails $success = wiki_xmlrpc_post('wiki.putPage', array($name, $content, $userid, $mypass), $args['url']); if (is_array($success)) { if ($success['code'] == 200) $reaction .= (_("OK").' '.$success['code']." ".$success['message']); else $reaction .= (_("FAILED").' '.$success['code']." ".$success['message']); } else $reaction .= (_("FAILED")); return $reaction; } // TODO: store log or checkpoint for restauration? function _upload($args, $path, $timeout) { global $request; $reaction = 'upload '; if ($args['noupload']) return ($reaction._("skipped")); //$userid = $request->_user->_userid; $url = $args['url']; $url = str_replace("/RPC2.php","/index.php", $url); $server = parse_url($url); $http = new HttpClient($server['host'], $server['port']); $http->timeout = $timeout + 5; $success = $http->postfile($server['url'], $path); if ($success) { if ($http->getStatus() == 200) $reaction .= _("OK"); else $reaction .= (_("FAILED").' '.$http->getStatus()); } else $reaction .= (_("FAILED").' '.$http->getStatus()." ".$http->errormsg); return $reaction; } }; // Local Variables: // mode: php // tab-width: 8 // c-basic-offset: 4 // c-hanging-comment-ender-p: nil // indent-tabs-mode: nil // End: ?>