1<?php 2 3/** 4 * All DokuWiki plugins to extend the admin function 5 * need to inherit from this class 6 */ 7class admin_plugin_sync extends DokuWiki_Admin_Plugin { 8 9 protected $profiles = array(); 10 protected $profno = ''; 11 protected $client = null; 12 protected $apiversion = 0; 13 protected $defaultTimeout = 15; 14 15 /** 16 * Constructor. 17 */ 18 function __construct(){ 19 $this->_profileLoad(); 20 $this->profno = preg_replace('/[^0-9]+/','',$_REQUEST['no']); 21 } 22 23 function _connect(){ 24 if(!is_null($this->client)) return true; 25 26 if ( isset($this->profiles[$this->profno]['timeout']) ){ 27 $timeout = (int) $this->profiles[$this->profno]['timeout']; 28 } else { 29 $timeout = $this->defaultTimeout; 30 } 31 32 if(class_exists('IXR_Client')) { 33 $this->client = new IXR_Client($this->profiles[$this->profno]['server'], false, 80, $timeout); 34 } 35 else { 36 $this->client = new dokuwiki\Remote\IXR\Client($this->profiles[$this->profno]['server']); 37 $this->client->timeout = $timeout; 38 } 39 40 // do the login 41 if($this->profiles[$this->profno]['user']){ 42 $ok = $this->client->query('dokuwiki.login', 43 $this->profiles[$this->profno]['user'], 44 $this->profiles[$this->profno]['pass'] 45 ); 46 if(!$ok){ 47 msg($this->getLang('xmlerr').' '.hsc($this->client->getErrorMessage()),-1); 48 $this->client = null; 49 return false; 50 } 51 if(!$this->client->getResponse()){ 52 msg($this->getLang('loginerr'),-1); 53 $this->client = null; 54 return false; 55 } 56 } 57 58 $ok = $this->client->query('dokuwiki.getXMLRPCAPIVersion'); 59 if(!$ok){ 60 msg($this->getLang('xmlerr').' '.hsc($this->client->getErrorMessage()),-1); 61 $this->client = null; 62 return false; 63 } 64 $this->apiversion = (int) $this->client->getResponse(); 65 if($this->apiversion < 1){ 66 msg($this->getLang('versionerr'),-1); 67 $this->client = null; 68 return false; 69 } 70 71 return true; 72 } 73 74 /** 75 * return sort order for position in admin menu 76 */ 77 function getMenuSort() { 78 return 1020; 79 } 80 81 /** 82 * handle profile saving/deleting 83 */ 84 function handle() { 85 if(isset($_REQUEST['prf']) && is_array($_REQUEST['prf'])){ 86 if(isset($_REQUEST['sync__delete']) && $this->profno !== ''){ 87 // delete profile 88 unset($this->profiles[$this->profno]); 89 $this->profiles = array_values($this->profiles); //reindex 90 $this->profno = ''; 91 }else{ 92 // add/edit profile 93 if($this->profno === '') $this->profno = count($this->profiles); 94 if ( !isset($_REQUEST['prf']['timeout']) || !is_numeric($_REQUEST['prf']['timeout']) ){ 95 $_REQUEST['prf']['timeout'] = $this->defaultTimeout; 96 } 97 $this->profiles[$this->profno] = $_REQUEST['prf']; 98 } 99 $this->_profileSave(); 100 101 // reset the client 102 $this->client = null; 103 } 104 } 105 106 /** 107 * output appropriate html 108 */ 109 function html() { 110 if(($_POST['sync_pages'] || $_POST['sync_media']) && $this->profno!==''){ 111 // do the sync 112 echo $this->locale_xhtml('sync'); 113 114 //show progressbar 115 echo '<div class="centeralign" id="dw__loading">'.NL; 116 echo '<script type="text/javascript" charset="utf-8"><!--//--><![CDATA[//><!--'.NL; 117 echo 'showLoadBar();'.NL; 118 echo '//--><!]]></script>'.NL; 119 echo '<br /></div>'.NL; 120 flush(); 121 ob_flush(); 122 123 echo '<ul class="sync">'; 124 125 if($_POST['sync_pages']){ 126 $this->_sync($_POST['sync_pages'],'pages'); 127 } 128 if($_POST['sync_media']){ 129 $this->_sync($_POST['sync_media'],'media'); 130 } 131 $this->_saveSyncTimes((int) $_POST['lnow'], 132 (int) $_POST['rnow']); 133 134 echo '</ul>'; 135 136 //hide progressbar 137 echo '<script type="text/javascript" charset="utf-8"><!--//--><![CDATA[//><!--'.NL; 138 echo 'hideLoadBar("dw__loading");'.NL; 139 echo '//--><!]]></script>'.NL; 140 flush(); 141 ob_flush(); 142 143 144 echo '<p>'.$this->getLang('syncdone').'</p>'; 145 }elseif($_REQUEST['startsync'] && $this->profno!==''){ 146 // get sync list 147 list($lnow,$rnow) = $this->_getTimes(); 148 $pages = array(); 149 $media = array(); 150 if($rnow){ 151 if($this->profiles[$this->profno]['type'] == 0 || 152 $this->profiles[$this->profno]['type'] == 1){ 153 $pages = $this->_getSyncList('pages'); 154 } 155 if(($this->profiles[$this->profno]['type'] == 0 || 156 $this->profiles[$this->profno]['type'] == 2) 157 && $pages !== false ){ 158 $media = $this->_getSyncList('media'); 159 } 160 } 161 162 if ( $pages === false || $media === false ){ 163 return; 164 } 165 166 if(count($pages) || count($media)){ 167 $this->_directionFormStart($lnow,$rnow); 168 if(count($pages)) 169 $this->_directionForm('pages',$pages); 170 if(count($media)) 171 $this->_directionForm('media',$media); 172 173 $this->_directionFormEnd(); 174 }else{ 175 echo $this->locale_xhtml('nochange'); 176 } 177 }else{ 178 echo $this->locale_xhtml('intro'); 179 180 echo '<div class="sync_left">'; 181 $this->_profilelist($this->profno); 182 if($this->profno !=='' ){ 183 echo '<br />'; 184 $this->_profileView($this->profno); 185 } 186 echo '</div>'; 187 echo '<div class="sync_right">'; 188 $this->_profileform($this->profno); 189 echo '</div>'; 190 } 191 } 192 193 /** 194 * Load profiles from serialized storage 195 */ 196 function _profileLoad(){ 197 global $conf; 198 $profiles = $conf['metadir'].'/sync.profiles'; 199 if(file_exists($profiles)){ 200 $this->profiles = unserialize(io_readFile($profiles,false)); 201 } 202 } 203 204 /** 205 * Save profiles to serialized storage 206 */ 207 function _profileSave(){ 208 global $conf; 209 $profiles = $conf['metadir'].'/sync.profiles'; 210 io_saveFile($profiles,serialize($this->profiles)); 211 } 212 213 /** 214 * Check connection for choosen profile and display last sync date. 215 */ 216 function _profileView(){ 217 if(!$this->_connect()) return false; 218 219 global $conf; 220 $no = $this->profno; 221 222 $ok = $this->client->query('dokuwiki.getVersion'); 223 $version = ''; 224 if($ok) $version = $this->client->getResponse(); 225 226 echo '<form action="" method="post">'; 227 echo '<input type="hidden" name="no" value="'.hsc($no).'" />'; 228 echo '<fieldset><legend>'.$this->getLang('syncstart').'</legend>'; 229 if($version){ 230 echo '<p>'.$this->getLang('remotever').' '.hsc($version).'</p>'; 231 if($this->profiles[$no]['ltime']){ 232 echo '<p>'.$this->getLang('lastsync').' '.strftime($conf['dformat'],$this->profiles[$no]['ltime']).'</p>'; 233 }else{ 234 echo '<p>'.$this->getLang('neversync').'</p>'; 235 } 236 echo '<input name="startsync" type="submit" value="'.$this->getLang('syncstart').'" class="button" />'; 237 }else{ 238 echo '<p class="error">'.$this->getLang('noconnect').'<br />'.hsc($this->client->getErrorMessage()).'</p>'; 239 } 240 echo '</fieldset>'; 241 echo '</form>'; 242 } 243 244 /** 245 * Dropdown list of available sync profiles 246 */ 247 function _profilelist($no=''){ 248 echo '<form action="" method="post">'; 249 echo '<fieldset><legend>'.$this->getLang('profile').'</legend>'; 250 echo '<select name="no" class="edit">'; 251 echo ' <option value="">'.$this->getLang('newprofile').'</option>'; 252 foreach($this->profiles as $pno => $opts){ 253 $srv = parse_url($opts['server']); 254 255 echo '<option value="'.hsc($pno).'" '.(($no!=='' && $pno == $no)?'selected="selected"':'').'>'; 256 echo ($pno+1).'. '; 257 if($opts['user']) echo hsc($opts['user']).'@'; 258 echo hsc($srv['host']); 259 if($opts['ns']) echo ':'.hsc($opts['ns']); 260 echo '</option>'; 261 } 262 echo '</select>'; 263 echo '<input type="submit" value="'.$this->getLang('select').'" class="button" />'; 264 echo '</fieldset>'; 265 echo '</form>'; 266 } 267 268 /** 269 * Form to edit or create a sync profile 270 */ 271 function _profileform($no=''){ 272 echo '<form action="" method="post" class="sync_profile">'; 273 echo '<fieldset><legend>'; 274 if($no !== ''){ 275 echo $this->getLang('edit'); 276 }else{ 277 echo $this->getLang('create'); 278 } 279 echo '</legend>'; 280 281 echo '<input type="hidden" name="no" value="'.hsc($no).'" />'; 282 283 echo '<label for="sync__server">'.$this->getLang('server').'</label> '; 284 echo '<input type="text" name="prf[server]" id="sync__server" class="edit" value="'.hsc($this->profiles[$no]['server']).'" />'; 285 echo '<samp>http://example.com/dokuwiki/lib/exe/xmlrpc.php</samp>'; 286 287 echo '<label for="sync__ns">'.$this->getLang('ns').'</label> '; 288 echo '<input type="text" name="prf[ns]" id="sync__ns" class="edit" value="'.hsc($this->profiles[$no]['ns']).'" />'; 289 290 echo '<label for="sync__depth">'.$this->getLang('depth').'</label> '; 291 echo '<select name="prf[depth]" id="sync__depth" class="edit">'; 292 echo '<option value="0" '.(($this->profiles[$no]['depth']==0)?'selected="selected"':'').'>'.$this->getLang('level0').'</option>'; 293 echo '<option value="1" '.(($this->profiles[$no]['depth']==1)?'selected="selected"':'').'>'.$this->getLang('level1').'</option>'; 294 echo '<option value="2" '.(($this->profiles[$no]['depth']==2)?'selected="selected"':'').'>'.$this->getLang('level2').'</option>'; 295 echo '<option value="3" '.(($this->profiles[$no]['depth']==3)?'selected="selected"':'').'>'.$this->getLang('level3').'</option>'; 296 echo '</select>'; 297 298 299 echo '<label for="sync__user">'.$this->getLang('user').'</label> '; 300 echo '<input type="text" name="prf[user]" id="sync__user" class="edit" value="'.hsc($this->profiles[$no]['user']).'" />'; 301 302 echo '<label for="sync__pass">'.$this->getLang('pass').'</label> '; 303 echo '<input type="password" name="prf[pass]" id="sync__pass" class="edit" value="'.hsc($this->profiles[$no]['pass']).'" />'; 304 305 echo '<label for="sync__timeout">'.$this->getLang('timeout').'</label>'; 306 echo '<input type="number" name="prf[timeout]" id="sync__timeout" class="edit" value="'.hsc($this->profiles[$no]['timeout']).'" />'; 307 308 echo '<span>'.$this->getLang('type').'</span>'; 309 310 echo '<div class="type">'; 311 echo '<input type="radio" name="prf[type]" id="sync__type0" value="0" '.(($this->profiles[$no]['type'] == 0)?'checked="checked"':'').'/>'; 312 echo '<label for="sync__type0">'.$this->getLang('type0').'</label> '; 313 314 echo '<input type="radio" name="prf[type]" id="sync__type1" value="1" '.(($this->profiles[$no]['type'] == 1)?'checked="checked"':'').'/>'; 315 echo '<label for="sync__type1">'.$this->getLang('type1').'</label> '; 316 317 echo '<input type="radio" name="prf[type]" id="sync__type2" value="2" '.(($this->profiles[$no]['type'] == 2)?'checked="checked"':'').'/>'; 318 echo '<label for="sync__type2">'.$this->getLang('type2').'</label> '; 319 echo '</div>'; 320 321 322 echo '<div class="submit">'; 323 echo '<input type="submit" value="'.$this->getLang('save').'" class="button" />'; 324 if($no !== '' && $this->profiles[$no]['ltime']){ 325 echo '<small>'.$this->getLang('changewarn').'</small>'; 326 } 327 echo '</div>'; 328 329 echo '<div class="submit">'; 330 echo '<input name="sync__delete" type="submit" value="'.$this->getLang('delete').'" class="button" />'; 331 echo '</div>'; 332 333 echo '</fieldset>'; 334 echo '</form>'; 335 } 336 337 /** 338 * Lock files that will be modified on either side. 339 * 340 * Lock fails are printed and removed from synclist 341 * 342 * @returns list of locked files 343 */ 344 function _lockFiles(&$synclist){ 345 if(!$this->_connect()) return array(); 346 // lock the files 347 $lock = array(); 348 foreach((array) $synclist as $id => $dir){ 349 if($dir == 0) continue; 350 if(checklock($id)){ 351 $this->_listOut($this->getLang('lockfail').' '.hsc($id),'error'); 352 unset($synclist[$id]); 353 }else{ 354 lock($id); // lock local 355 $lock[] = $id; 356 } 357 } 358 // lock remote files 359 $ok = $this->client->query('dokuwiki.setLocks',array('lock'=>$lock,'unlock'=>array())); 360 if(!$ok){ 361 $this->_listOut('failed RPC communication'); 362 $synclist = array(); 363 return array(); 364 } 365 $data = $this->client->getResponse(); 366 foreach((array) $data['lockfail'] as $id){ 367 $this->_listOut($this->getLang('lockfail').' '.hsc($id),'error'); 368 unset($synclist[$id]); 369 } 370 371 return $lock; 372 } 373 374 /** 375 * Print a message as list item using the given class 376 */ 377 function _listOut($msg,$class='ok'){ 378 echo '<li class="'.hsc($class).'"><div class="li">'; 379 echo hsc($msg); 380 echo "</div></li>\n"; 381 flush(); 382 ob_flush(); 383 } 384 385 /** 386 * Execute the sync action and print the results 387 */ 388 function _sync(&$synclist,$type){ 389 if(!$this->_connect()) return false; 390 $no = $this->profno; 391 $sum = $_REQUEST['sum']; 392 393 if($type == 'pages') 394 $lock = $this->_lockfiles($synclist); 395 396 // do the sync 397 foreach((array) $synclist as $id => $dir){ 398 @set_time_limit(30); 399 if($dir == 0){ 400 $this->_listOut($this->getLang('skipped').' '.$id,'skipped'); 401 continue; 402 } 403 if($dir == -2){ 404 //delete local 405 if($type == 'pages'){ 406 saveWikiText($id,'',$sum,false); 407 $this->_listOut($this->getLang('localdelok').' '.$id,'del_okay'); 408 }else{ 409 if(unlink(mediaFN($id))){ 410 $this->_listOut($this->getLang('localdelok').' '.$id,'del_okay'); 411 }else{ 412 $this->_listOut($this->getLang('localdelfail').' '.$id,'del_fail'); 413 } 414 } 415 continue; 416 } 417 if($dir == -1){ 418 //pull 419 if($type == 'pages'){ 420 $ok = $this->client->query('wiki.getPage',$id); 421 }else{ 422 $ok = $this->client->query('wiki.getAttachment',$id); 423 } 424 if(!$ok){ 425 $this->_listOut($this->getLang('pullfail').' '.$id.' '. 426 $this->client->getErrorMessage(),'pull_fail'); 427 continue; 428 } 429 $data = $this->client->getResponse(); 430 if($type == 'pages'){ 431 saveWikiText($id,$data,$sum,false); 432 idx_addPage($id); 433 }else{ 434 if($this->apiversion < 7){ 435 $data = base64_decode($data); 436 } 437 io_saveFile(mediaFN($id),$data); 438 } 439 $this->_listOut($this->getLang('pullok').' '.$id,'pull_okay'); 440 continue; 441 } 442 if($dir == 1){ 443 // push 444 if($type == 'pages'){ 445 $data = rawWiki($id); 446 $ok = $this->client->query('wiki.putPage',$id,$data,array('sum'=>$sum)); 447 }else{ 448 $data = io_readFile(mediaFN($id),false); 449 if($this->apiversion < 6){ 450 $data = base64_encode($data); 451 }else{ 452 $data = new IXR_Base64($data); 453 } 454 $ok = $this->client->query('wiki.putAttachment',$id,$data,array('ow'=>true)); 455 } 456 if(!$ok){ 457 $this->_listOut($this->getLang('pushfail').' '.$id.' '. 458 $this->client->getErrorMessage(),'push_fail'); 459 continue; 460 } 461 $this->_listOut($this->getLang('pushok').' '.$id,'push_okay'); 462 continue; 463 } 464 if($dir == 2){ 465 // remote delete 466 if($type == 'pages'){ 467 $ok = $this->client->query('wiki.putPage',$id,'',array('sum'=>$sum)); 468 }else{ 469 $ok = $this->client->query('wiki.deleteAttachment',$id); 470 } 471 if(!$ok){ 472 $this->_listOut($this->getLang('remotedelfail').' '.$id.' '. 473 $this->client->getErrorMessage(),'del_fail'); 474 continue; 475 } 476 $this->_listOut($this->getLang('remotedelok').' '.$id,'del_okay'); 477 continue; 478 } 479 } 480 481 // unlock 482 if($type == 'pages'){ 483 foreach((array) $synclist as $id => $dir){ 484 unlock($id); 485 } 486 $this->client->query('dokuwiki.setLocks',array('lock'=>array(),'unlock'=>$lock)); 487 } 488 489 490 } 491 492 /** 493 * Save synctimes 494 */ 495 function _saveSyncTimes($ltime,$rtime){ 496 $no = $this->profno; 497 list($letime,$retime) = $this->_getTimes(); 498 $this->profiles[$no]['ltime'] = $ltime; 499 $this->profiles[$no]['rtime'] = $rtime; 500 $this->profiles[$no]['letime'] = $letime; 501 $this->profiles[$no]['retime'] = $retime; 502 $this->_profileSave(); 503 } 504 505 /** 506 * Open the sync direction form and initialize the table 507 */ 508 function _directionFormStart($lnow,$rnow){ 509 $no = $this->profno; 510 echo $this->locale_xhtml('list'); 511 echo '<form action="" method="post">'; 512 echo '<table class="inline" id="sync__direction__table">'; 513 echo '<input type="hidden" name="lnow" value="'.$lnow.'" />'; 514 echo '<input type="hidden" name="rnow" value="'.$rnow.'" />'; 515 echo '<input type="hidden" name="no" value="'.$no.'" />'; 516 echo '<tr> 517 <th class="sync__file">'.$this->getLang('file').'</th> 518 <th class="sync__local">'.$this->getLang('local').'</th> 519 <th class="sync__push" id="sync__push">></th> 520 <th class="sync__skip" id="sync__skip">=</th> 521 <th class="sync__pull" id="sync__pull"><</th> 522 <th class="sync__remote">'.$this->getLang('remote').'</th> 523 <th class="sync__diff">'.$this->getLang('diff').'</th> 524 </tr>'; 525 } 526 527 /** 528 * Close the direction form and table 529 */ 530 function _directionFormEnd(){ 531 global $lang; 532 echo '</table>'; 533 echo '<label for="the__summary">'.$lang['summary'].'</label> '; 534 echo '<input type="text" name="sum" id="the__summary" value="" class="edit" />'; 535 echo '<input type="submit" value="'.$this->getLang('syncstart').'" class="button" />'; 536 echo '</form>'; 537 } 538 539 /** 540 * Print a list of changed files and ask for the sync direction 541 * 542 * Tries to be clever about suggesting the direction 543 */ 544 function _directionForm($type,&$synclist){ 545 global $conf; 546 global $lang; 547 $no = $this->profno; 548 549 $ltime = (int) $this->profiles[$no]['ltime']; 550 $rtime = (int) $this->profiles[$no]['rtime']; 551 $letime = (int) $this->profiles[$no]['letime']; 552 $retime = (int) $this->profiles[$no]['retime']; 553 554 foreach($synclist as $id => $item){ 555 // check direction 556 $dir = 0; 557 if($ltime && $rtime){ // synced before 558 if($item['remote']['mtime'] > $rtime && 559 $item['local']['mtime'] <= $letime){ 560 $dir = -1; 561 } 562 if($item['remote']['mtime'] <= $retime && 563 $item['local']['mtime'] > $ltime){ 564 $dir = 1; 565 } 566 }else{ // never synced 567 if(!$item['local']['mtime'] && $item['remote']['mtime']){ 568 $dir = -1; 569 } 570 if($item['local']['mtime'] && !$item['remote']['mtime']){ 571 $dir = 1; 572 } 573 } 574 575 echo '<tr>'; 576 577 echo '<td class="sync__file">'.hsc($id).'</td>'; 578 echo '<td class="sync__local">'; 579 if(!isset($item['local'])){ 580 echo '—'; 581 }else{ 582 echo '<div>'.strftime($conf['dformat'],$item['local']['mtime']).'</div>'; 583 echo ' <div>('.$item['local']['size'].' bytes)</div>'; 584 } 585 echo '</td>'; 586 587 echo '<td class="sync__push">'; 588 if(!isset($item['local'])){ 589 echo '<input type="radio" name="sync_'.$type.'['.hsc($id).']" value="2" class="syncpush" title="'.$this->getLang('pushdel').'" '.(($dir == 2)?'checked="checked"':'').' />'; 590 }else{ 591 echo '<input type="radio" name="sync_'.$type.'['.hsc($id).']" value="1" class="syncpush" title="'.$this->getLang('push').'" '.(($dir == 1)?'checked="checked"':'').' />'; 592 } 593 echo '</td>'; 594 echo '<td class="sync__skip">'; 595 echo '<input type="radio" name="sync_'.$type.'['.hsc($id).']" value="0" class="syncskip" title="'.$this->getLang('keep').'" '.(($dir == 0)?'checked="checked"':'').' />'; 596 echo '</td>'; 597 echo '<td class="sync__pull">'; 598 if(!isset($item['remote'])){ 599 echo '<input type="radio" name="sync_'.$type.'['.hsc($id).']" value="-2" class="syncpull" title="'.$this->getLang('pulldel').'" '.(($dir == -2)?'checked="checked"':'').' />'; 600 }else{ 601 echo '<input type="radio" name="sync_'.$type.'['.hsc($id).']" value="-1" class="syncpull" title="'.$this->getLang('pull').'" '.(($dir == -1)?'checked="checked"':'').' />'; 602 } 603 echo '</td>'; 604 605 echo '<td class="sync__remote">'; 606 if(!isset($item['remote'])){ 607 echo '—'; 608 }else{ 609 echo '<div>'.strftime($conf['dformat'],$item['remote']['mtime']).'</div>'; 610 echo ' <div>('.$item['remote']['size'].' bytes)</div>'; 611 } 612 echo '</td>'; 613 614 echo '<td class="sync__diff">'; 615 if($type == 'pages'){ 616 echo '<a href="'.DOKU_BASE.'lib/plugins/sync/diff.php?id='.$id.'&no='.$no.'" target="_blank" class="sync_popup">'.$this->getLang('diff').'</a>'; 617 } 618 echo '</td>'; 619 620 echo '</tr>'; 621 } 622 } 623 624 /** 625 * Get the local and remote time 626 */ 627 function _getTimes(){ 628 if(!$this->_connect()) return false; 629 // get remote time 630 $ok = $this->client->query('dokuwiki.getTime'); 631 if(!$ok){ 632 msg('Failed to fetch remote time. '. 633 $this->client->getErrorMessage(),-1); 634 return false; 635 } 636 $rtime = $this->client->getResponse(); 637 $ltime = time(); 638 return array($ltime,$rtime); 639 } 640 641 /** 642 * Get a list of changed files 643 */ 644 function _getSyncList($type='pages'){ 645 if(!$this->_connect()) return array(); 646 global $conf; 647 $no = $this->profno; 648 $list = array(); 649 $ns = $this->profiles[$no]['ns']; 650 651 // get remote file list 652 if($type == 'pages'){ 653 $ok = $this->client->query('dokuwiki.getPagelist',$ns, 654 array('depth' => (int) $this->profiles[$no]['depth'], 655 'hash' => true)); 656 }else{ 657 $ok = $this->client->query('wiki.getAttachments',$ns, 658 array('depth' => (int) $this->profiles[$no]['depth'], 659 'hash' => true)); 660 } 661 if(!$ok){ 662 msg('Failed to fetch remote file list. '. 663 $this->client->getErrorMessage(),-1); 664 return false; 665 } 666 $remote = $this->client->getResponse(); 667 // put into synclist 668 foreach($remote as $item){ 669 $list[$item['id']]['remote'] = $item; 670 unset($list[$item['id']]['remote']['id']); 671 } 672 unset($remote); 673 674 // get local file list 675 $local = array(); 676 $dir = utf8_encodeFN(str_replace(':', '/', $ns)); 677 require_once(DOKU_INC.'inc/search.php'); 678 if($type == 'pages'){ 679 search($local, $conf['datadir'], 'search_allpages', 680 array('depth' => (int) $this->profiles[$no]['depth'], 681 'hash' => true), $dir); 682 }else{ 683 search($local, $conf['mediadir'], 'search_media', 684 array('depth' => (int) $this->profiles[$no]['depth'], 685 'hash' => true), $dir); 686 } 687 688 // put into synclist 689 foreach($local as $item){ 690 // skip identical files 691 if($list[$item['id']]['remote']['hash'] == $item['hash']){ 692 unset($list[$item['id']]); 693 continue; 694 } 695 696 $list[$item['id']]['local'] = $item; 697 unset($list[$item['id']]['local']['id']); 698 } 699 unset($local); 700 701 ksort($list); 702 return $list; 703 } 704 705 /** 706 * show diff between the local and remote versions of the page 707 */ 708 function _diff($id){ 709 if(!$this->_connect()) return false; 710 $no = $this->profno; 711 712 $ok = $this->client->query('wiki.getPage',$id); 713 if(!$ok){ 714 echo $this->getLang('pullfail').' '.hsc($id).' '; 715 echo hsc($this->client->getErrorMessage()); 716 die(); 717 } 718 $remote = $this->client->getResponse(); 719 $local = rawWiki($id); 720 721 $df = new Diff(explode("\n",htmlspecialchars($local)), 722 explode("\n",htmlspecialchars($remote))); 723 724 $tdf = new TableDiffFormatter(); 725 echo '<table class="diff">'; 726 echo '<tr>'; 727 echo '<th colspan="2">'.$this->getLang('local').'</th>'; 728 echo '<th colspan="2">'.$this->getLang('remote').'</th>'; 729 echo '</tr>'; 730 echo $tdf->format($df); 731 echo '</table>'; 732 } 733} 734//Setup VIM: ex: et ts=4 enc=utf-8 : 735