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