1<?php 2/** 3 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 4 * @author Andreas Gohr <andi@splitbrain.org> 5 */ 6// must be run within Dokuwiki 7if(!defined('DOKU_INC')) die(); 8 9if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 10require_once(DOKU_PLUGIN.'syntax.php'); 11require_once(DOKU_INC.'inc/infoutils.php'); 12 13 14/** 15 * This is the base class for all syntax classes, providing some general stuff 16 */ 17class helper_plugin_freesync extends DokuWiki_Plugin { 18 19 var $_profilePath; 20 var $_client = null; 21 var $_profile = array(); 22 23 /** 24 * constructor 25 */ 26 function helper_plugin_freesync(){ 27 global $conf; 28 29 $this->_profilePath = $conf['cachedir'].'/freesync'; 30 if(!file_exists($this->_profilePath)) 31 @mkdir($this->_profilePath); 32 } 33 34 /** 35 * return some info 36 */ 37 function getInfo(){ 38 return array( 39 'author' => 'Mikhail I. Izmestev', 40 'email' => 'izmmishao5@gmail.com', 41 'date' => '2009-03-11', 42 'name' => 'freesync helper', 43 'desc' => 'Dirty work for freesync', 44 'url' => '', 45 ); 46 } 47 48 function getProfileName() { 49 return $this->_profile['name']; 50 } 51 52 function getProfile() { 53 return $this->_profile; 54 } 55 56 function saveProfile($oldname, $profile) { 57 $this->loadProfile($oldname); 58 $this->_profile["pages"] = null; 59 $this->_profile["files"] = null; 60 61 $profile = array_merge($this->_profile, $profile); 62 $oldpfFN = $this->_profilePath.'/'.$oldname.".ini"; 63 if(preg_match('/^http:\/\/([a-zA-Z0-9][a-zA-Z0-9_.]+)\//', $profile['xmlrpcurl'], $match)) { 64 $profile['name'] = $match[1]; 65 $pfFN = $this->_profilePath.'/'.$profile['name'].".ini"; 66 $fprofile = fopen($pfFN, "w"); 67 foreach($profile as $key => $val) { 68 fprintf($fprofile, "%s=\"%s\"\n", $key, $val); 69 } 70 fclose($fprofile); 71 72 if($oldpfFN != $pfFN && file_exists($oldpfFN)) 73 unlink($oldpfFN); 74 75 $this->loadProfile($profile['name']); 76 } 77 elseif(empty($profile['xmlrpcurl']) && file_exists($oldpfFN)) 78 unlink($oldpfFN); 79 else 80 msg('Bad XMLRPC URL'); 81 } 82 83 function getProfiles() { 84 $profiles = array(); 85 if($dh = opendir($this->_profilePath)) { 86 while($profile = readdir($dh)) { 87 if(preg_match("/^(.*).ini$/", $profile, $match)) 88 $profiles[] = $match[1]; 89 } 90 closedir($dh); 91 } 92 return $profiles; 93 } 94 95 function loadProfile($name) { 96 $pfFN = $this->_profilePath.'/'.$name.".ini"; 97 if(file_exists($pfFN)) 98 $this->_profile = parse_ini_file($pfFN); 99 else 100 $this->_profile = array(); 101 if(!empty($this->_profile['namespace']) && !preg_match('/.*:$/', $this->_profile['namespace'])) 102 $this->_profile['namespace'] .= ":"; 103 } 104 105 function _xmlrpcInit() { 106 global $conf; 107 require_once(DOKU_INC.'inc/IXR_Library.php'); 108 109 if(!$this->_client) { 110 $this->_client = new IXR_Client($this->_profile['xmlrpcurl']); 111 if(!empty($this->_profile['user'])) { 112 $loginurl = str_replace('lib/exe/xmlrpc.php', 'doku.php?do=login', $this->_profile['xmlrpcurl']); 113 $this->_client->sendRequest($loginurl); 114 $this->_client->sendRequest($loginurl, 115 array("id" => "start", "u" => $this->_profile['user'], "p" => $this->_profile['pass']), 116 'POST'); 117 } 118 } 119 } 120 121 function _rpcQuery($call, $data, &$response) { 122 if(!$this->_client) 123 $this->_xmlrpcInit(); 124 125 if(!is_array($data)) 126 $data = array($data); 127 128 array_unshift($data, $call); 129 130 if(call_user_func_array(array(&$this->{'_client'},"query"), $data)) { 131 $response = $this->_client->getResponse(); 132 return TRUE; 133 } 134 135 return false; 136 } 137 138 139 140 function getPagesDiff() { 141 global $conf; 142 143 $remote_list = array(); 144 $local_list = array(); 145 if($this->_profile['pages']) { 146 if($this->_rpcQuery('wiki.getAllPages', array(), $rpages)) { 147 foreach($rpages as $page) { 148 $remote_list[$page["id"]] = array("id" => $page["id"], 149 "rperms" => $page["perms"], 150 "rsize" => $page["size"], 151 "rlastModified" => $page["lastModified"]->getTimestamp()); 152 } 153 154 $pages = file($conf['indexdir'] . '/page.idx'); 155 foreach(array_keys($pages) as $idx) { 156 if(page_exists($pages[$idx])) { 157 $perm = auth_quickaclcheck($pages[$idx]); 158 if($perm >= AUTH_READ) { 159 $local_list[trim($pages[$idx])] = array("id" => trim($pages[$idx]), 160 "lperms" => $perm, 161 "lsize" => @filesize(wikiFN($pages[$idx])), 162 "llastModified" => @filemtime(wikiFN($pages[$idx]))); 163 } 164 } 165 } 166 167 } 168 else 169 msg("Error get remote list pages"); 170 } 171 if($this->_profile['files']) { 172 if($this->_rpcQuery('wiki.getAttachments', array("", array("recursive" => true)), $rfiles)) { 173 if(count($rfiles)) 174 foreach($rfiles as $file) { 175 $remote_list[$file["id"]] = array("id" => $file["id"], 176 "rperms" => $file["perms"], 177 "rsize" => $file["size"], 178 "rlastModified" => $file["lastModified"]->getTimestamp(), 179 "file" => 1); 180 } 181 182 if(auth_quickaclcheck(':*') >= AUTH_READ) { 183 $dir = utf8_encodeFN(str_replace(':', '/', $ns)); 184 185 $lfiles = array(); 186 require_once(DOKU_INC.'inc/search.php'); 187 search($lfiles, $conf['mediadir'], 'search_media', array('recursive' => true), $dir); 188 189 if(count($lfiles)) 190 foreach($lfiles as $file) { 191 $local_list[$file["id"]] = array("id" => $file["id"], 192 "lperms" => auth_quickaclcheck(getNS($file['id']).':*'), 193 "lsize" => $file["size"], 194 "llastModified" => $file["mtime"], 195 "file" => 1); 196 } 197 } 198 } 199 else 200 msg("Error get remote list files"); 201 } 202 203 $sync_list = array_merge_recursive($remote_list, $local_list); 204 205 $sync_list = array_filter($sync_list, array(&$this, '_pageFilter')); 206 207 ksort($sync_list); 208 return $sync_list; 209 210 } 211 212 function _pageFilter($page) { 213 $id = is_array($page['id'])?$page['id'][0]:$page['id']; 214 if($page['llastModified'].','.$page['rlastModified'] == $this->_profile[$id]) { 215 return 0; 216 } 217 return !strncmp($id, $this->_profile['namespace'], strlen($this->_profile['namespace'])) && 218 $page['llastModified'] != $page['rlastModified']; 219 } 220 221 function getLocalPage($id, $onlyinfo = false) { 222 if(empty($id)) return false; 223 224 $file = wikiFN($id,''); 225 $time = @filemtime($file); 226 if(!$time){ 227 return false; 228 } 229 230 $info = getRevisionInfo($id, $time, 1024); 231 232 $page = array( 233 'name' => $id, 234 'author' => (($info['user']) ? $info['user'] : $info['ip']), 235 'version' => $time 236 ); 237 238 if($onlyinfo) 239 return $page; 240 241 $page['text'] = rawWiki($id,''); 242 if(!$page['text']) { 243 $data = array($id); 244 $page['text'] = trigger_event('HTML_PAGE_FROMTEMPLATE',$data,'pageTemplate',true); 245 } 246 247 return $page; 248 } 249 250 function getRemotePage($id, $onlyinfo = false) { 251 if(empty($id)) return false; 252 if($this->_rpcQuery('wiki.getPageInfo', $id, $page)) { 253 if($page['version']) { 254 if(!$onlyinfo) { 255 $page['text'] = false; 256 $this->_rpcQuery('wiki.getPage', $id, $page['text']); 257 } 258 return $page; 259 } 260 } 261 262 return false; 263 } 264 265 function page_l2r($id, $sum='') { 266 if(! ($page = $this->getLocalPage($id)) ) { 267 $page['text'] = ''; 268 } 269 if($this->_rpcQuery('wiki.putPage', array($id, $page['text'], $sum), $res)) { 270 if($res == 0) { 271 $rversion = $this->getRemotePage($id, true); 272 $this->_profile[$id] = $page['version'].','.$rversion['lastModified']->getTimestamp(); 273 $this->saveProfile($this->getProfileName(), $this->_profile); 274 return "Succesful"; 275 } 276 } 277 return "Error!"; 278 } 279 280 281 function page_r2l($id, $sum='') { 282 global $TEXT; 283 global $lang; 284 global $conf; 285 286 $id = cleanID($id); 287 if(empty($id)) 288 return 'Empty page ID'; 289 290 $page = $this->getRemotePage($id); 291 if(!$page || !$page['text']) 292 return 'Error getting page'; 293 294 $TEXT = trim($page['text']); 295 296 297 if(!page_exists($id) && empty($TEXT)) { 298 return 'Refusing to write an empty new wiki page'; 299 } 300 301 if(auth_quickaclcheck($id) < AUTH_EDIT) 302 return 'You are not allowed to edit this page'; 303 304 // Check, if page is locked 305 if(checklock($id)) 306 return 'The page is currently locked'; 307 308 // SPAM check 309 if(checkwordblock()) 310 return 'Positive wordblock check'; 311 312 // autoset summary on new pages 313 if(!page_exists($id) && empty($sum)) { 314 $sum = $lang['created']; 315 } 316 317 // autoset summary on deleted pages 318 if(page_exists($id) && empty($TEXT) && empty($sum)) { 319 $sum = $lang['deleted']; 320 } 321 322 lock($id); 323 324 saveWikiText($id,$TEXT,$sum); 325 326 unlock($id); 327 328 // run the indexer if page wasn't indexed yet 329 if(!@file_exists(metaFN($id, '.indexed'))) { 330 // try to aquire a lock 331 $lock = $conf['lockdir'].'/_indexer.lock'; 332 while(!@mkdir($lock,$conf['dmode'])){ 333 usleep(50); 334 if(time()-@filemtime($lock) > 60*5){ 335 // looks like a stale lock - remove it 336 @rmdir($lock); 337 }else{ 338 return "Lock time out"; 339 } 340 } 341 if($conf['dperm']) chmod($lock, $conf['dperm']); 342 343 require_once(DOKU_INC.'inc/indexer.php'); 344 345 // do the work 346 idx_addPage($id); 347 348 // we're finished - save and free lock 349 io_saveFile(metaFN($id,'.indexed'),INDEXER_VERSION); 350 @rmdir($lock); 351 } 352 353 $lversion = $this->getLocalPage($id, true); 354 $this->_profile[$id] = $lversion['version'].','.$page['lastModified']->getTimestamp(); 355 $this->saveProfile($this->getProfileName(), $this->_profile); 356 return "Succesful"; 357 } 358 359 function getLocalFile($id, $onlyinfo = false) { 360 if(empty($id)) return false; 361 if (auth_quickaclcheck(getNS($id).':*') < AUTH_READ) 362 return false; 363 364 $filename = mediaFN($id); 365 if (!@ file_exists($filename)) 366 return false; 367 368 $file['lastModified'] = filemtime($filename); 369 $file['size'] = filesize($filename); 370 371 if($onlyinfo) 372 return $file; 373 374 $data = io_readFile($filename, false); 375 $file['content'] = base64_encode($data); 376 return $file; 377 } 378 379 function getRemoteFile($id, $onlyinfo = false) { 380 if(empty($id)) return false; 381 if(!$this->_client) 382 $this->_xmlrpcInit(); 383 if($this->_rpcQuery('wiki.getAttachmentInfo', $id, $file) && $file['lastModified']) { 384 $file['lastModified'] = $file['lastModified']->getTimestamp(); 385 if(!$onlyinfo) { 386 $file['content'] = false; 387 $fetchurl = str_replace('xmlrpc.php', 'fetch.php?media='.$id, $this->_profile['xmlrpcurl']); 388 $this->_client->sendRequest($fetchurl); 389 if(strlen($this->_client->resp_body) && $this->_client->resp_body != "Not Found") 390 $file['content'] = base64_encode($this->_client->resp_body); 391 } 392 return $file; 393 } 394 return false; 395 } 396 397 function file_l2r($id, $sum='') { 398 $id = cleanID($id); 399 $file = $this->getLocalFile($id); 400 if($file && $this->_rpcQuery('wiki.putAttachment', array($id, $file['content'], array("ow" => true)), $res)) { 401 402 $rversion = $this->getRemoteFile($id, true); 403 $this->_profile[$id] = $file['lastModified'].','.$rversion['lastModified']; 404 $this->saveProfile($this->getProfileName(), $this->_profile); 405 return "Succesful"; 406 } 407 return "Error!"; 408 } 409 410 411 function file_r2l($id, $sum='') { 412 global $conf; 413 global $lang; 414 415 if(!isset($id)) { 416 return 'Filename not given.'; 417 } 418 419 $file = $this->getRemoteFile($id); 420 if(!$file) { 421 return 'Error get remote file'; 422 } 423 if(!$file['content']) { 424 return 'File is empty'; 425 } 426 427 $auth = auth_quickaclcheck(getNS($id).':*'); 428 if($auth >= AUTH_UPLOAD) { 429 430 $ftmp = $conf['tmpdir'] . '/' . $id; 431 432 // save temporary file 433 @unlink($ftmp); 434 $buff = base64_decode($file['content']); 435 io_saveFile($ftmp, $buff); 436 437 // get filename 438 list($iext, $imime,$dl) = mimetype($id); 439 $id = cleanID($id); 440 $fn = mediaFN($id); 441 442 // get filetype regexp 443 $types = array_keys(getMimeTypes()); 444 $types = array_map(create_function('$q','return preg_quote($q,"/");'),$types); 445 $regex = join('|',$types); 446 447 // because a temp file was created already 448 if(preg_match('/\.('.$regex.')$/i',$fn)) { 449 //check for overwrite 450 $overwrite = @file_exists($fn); 451 if($overwrite && $auth < AUTH_DELETE) { 452 return $lang['uploadexist']; 453 } 454 // check for valid content 455 @require_once(DOKU_INC.'inc/media.php'); 456 $ok = media_contentcheck($ftmp, $imime); 457 if($ok == -1) { 458 return sprintf($lang['uploadexist'], ".$iext"); 459 } elseif($ok == -2) { 460 return $lang['uploadspam']; 461 } elseif($ok == -3) { 462 return $lang['uploadxss']; 463 } 464 465 // prepare event data 466 $data[0] = $ftmp; 467 $data[1] = $fn; 468 $data[2] = $id; 469 $data[3] = $imime; 470 $data[4] = $overwrite; 471 472 // trigger event 473 require_once(DOKU_INC.'inc/events.php'); 474 if(true == trigger_event('MEDIA_UPLOAD_FINISH', $data, array($this, '_media_upload_action'), true)) { 475 $lversion = $this->getLocalFile($id, true); 476 $this->_profile[$id] = $lversion['lastModified'].','.$file['lastModified']; 477 $this->saveProfile($this->getProfileName(), $this->_profile); 478 return "Succesful"; 479 } 480 481 } else { 482 return $lang['uploadwrong']; 483 } 484 } else { 485 return "You don't have permissions to upload files."; 486 } 487 } 488 489 /** 490 * Moves the temporary file to its final destination. 491 * 492 * Michael Klier <chi@chimeric.de> 493 */ 494 function _media_upload_action($data) { 495 global $conf; 496 497 if(is_array($data) && count($data)===5) { 498 io_createNamespace($data[2], 'media'); 499 if(rename($data[0], $data[1])) { 500 chmod($data[1], $conf['fmode']); 501 media_notify($data[2], $data[1], $data[3]); 502 // add a log entry to the media changelog 503 require_once(DOKU_INC.'inc/changelog.php'); 504 if ($data[4]) { 505 addMediaLogEntry(time(), $data[2], DOKU_CHANGE_TYPE_EDIT); 506 } else { 507 addMediaLogEntry(time(), $data[2], DOKU_CHANGE_TYPE_CREATE); 508 } 509 return true; 510 } else { 511 return 'Upload failed.'; 512 } 513 } else { 514 return 'Upload failed.'; 515 } 516 } 517 518 function getDiff($id) { 519 $id = cleanID($id); 520 if(empty($id)) 521 return 'Empty page ID'; 522 $lpage = $this->getLocalPage($id); 523 $rpage = $this->getRemotePage($id); 524 525 require_once(DOKU_INC.'inc/DifferenceEngine.php'); 526 527 $tdf = new TableDiffFormatter(); 528 return '<div class="dokuwiki"><table class="diff">'. 529 '<tr><th colspan="2">Local Wiki</th><th colspan="2">Remote Wiki</th></tr>'. 530 $tdf->format(new Diff(split("\n", $lpage['text']), split("\n", $rpage['text']))). 531 '</table></div>'; 532 } 533 534} 535