1<?php
2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
3//
4// All Rights Reserved. See copyright.txt for details and a complete list of authors.
5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
6// $Id$
7
8//this script may only be included - so its better to die if called directly.
9if (strpos($_SERVER['SCRIPT_NAME'], basename(__FILE__)) !== false) {
10	header('location: index.php');
11	exit;
12}
13define('WIKI_XML', 'wiki.xml');
14
15class XmlLib extends TikiLib
16{
17	public $errors = [];
18	public $errorsArgs = [];
19	public $xml = '';
20	public $zip = '';
21	public $config = ['comments' => true, 'attachments' => true, 'history' => true, 'images' => true, 'debug' => false];
22	public $structureStack = [];
23
24	function get_error()
25	{
26		$str = '';
27		foreach ($this->errors as $i => $error) {
28			$str = $error;
29			if (is_array($this->errorsArgs[$i])) {
30				$str .= ': ' . implode(', ', $this->errorsArgs[$i]);
31			} else {
32				$str .= ': ' . $this->errorsArgs[$i];
33			}
34		}
35		return $str;
36	}
37
38	/* Export a list of pages or a structure */
39	function export_pages($pages = null, $structure = null, $zipFile = 'dump/xml.zip', $config = null)
40	{
41		if (! class_exists('ZipArchive')) {
42			$this->errors[] = 'Problem zip initialisation';
43			$this->errorsArgs[] = 'ZipArchive class not found';
44			return false;
45		}
46
47		$this->zip = new ZipArchive;
48
49		if (! $this->zip->open($zipFile, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE)) {
50			$this->errors[] = 'The file cannot be opened';
51			$this->errorsArgs[] = $zipFile;
52			return false;
53		}
54
55		if (! empty($config)) {
56			$this->config = array_merge($this->config, $config);
57		}
58
59		$this->xml .= '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
60
61		if (count($pages) >= 1) {
62			$this->xml .= "<pages>\n";
63			foreach ($pages as $page) {
64				if (! $this->export_page($page)) {
65					return false;
66				}
67			}
68			$this->xml .= "</pages>\n";
69		}
70
71		if (! empty($structure)) {
72			$structlib = TikiLib::lib('struct');
73			$pages = $structlib->s_get_structure_pages($structure);
74			$stack = [];
75			foreach ($pages as $page) {
76				while (count($stack) && $stack[count($stack) - 1] != $page['parent_id']) {
77					array_pop($stack);
78					$this->xml .= "</structure>\n";
79				}
80				$this->xml .= "<structure>\n";
81				$stack[] = $page['page_ref_id'];
82				if (! $this->export_page($page['pageName'])) {
83					return false;
84				}
85			}
86
87			while (count($stack)) {
88				array_pop($stack);
89				$this->xml .= "</structure>\n";
90			}
91		}
92
93		if (! $this->zip->addFromString(WIKI_XML, $this->xml)) {
94			$this->errors[] = 'Can not add the xml';
95			$this->errorsArgs[] = WIKI_XML;
96			return false;
97		}
98		if ($this->config['debug']) {
99			echo '<pre>' . htmlspecialchars($this->xml) . '</pre>';
100		}
101		$this->zip->close();
102		return true;
103	}
104
105	/* export one page */
106	function export_page($page)
107	{
108		global $prefs, $tikidomain;
109		$tikilib = TikiLib::lib('tiki');
110		$smarty = TikiLib::lib('smarty');
111		$parserlib = TikiLib::lib('parser');
112		$info = $tikilib->get_page_info($page);
113
114		if (empty($info)) {
115			$this->errors[] = 'Page does not exist';
116			$this->errorsArgs[] = $page;
117			return false;
118		}
119
120		$dir = $page;
121		$info['zip'] = "$dir/" . $page;
122		$smarty->assign_by_ref('info', $info);
123
124		if (! $this->zip->addFromString($info['zip'], $info['data'])) {
125			$this->errors[] = 'Can not add the page';
126			$this->errorsArgs[] = $info['zip'];
127			return false;
128		}
129
130		if ($prefs['feature_wiki_comments'] == 'y' && $this->config['comments']) {
131			$commentslib = TikiLib::lib('comments');
132			$comments = $commentslib->get_comments('wiki page:' . $page, 0, 0, 0, 'commentDate_asc', '', 0, 'commentStyle_plain');
133			if (! empty($comments['cant'])) {
134				$smarty->assign_by_ref('comments', $comments['data']);
135			}
136		}
137		$images = [];
138
139		if ($prefs['feature_wiki_pictures'] == 'y'
140				&& $this->config['images']
141				&& preg_match_all('/\{img\s*\(?([^\}]+)\)?\s*\}/i', $info['data'], $matches)
142		) {
143			global $tikiroot;
144			foreach ($matches[1] as $match) {
145				$args = $parserlib->plugin_split_args($match);
146				if (! empty($args['src']) && preg_match('|img/wiki_up/(.*)|', $args['src'], $m)) {
147					$file = empty($tikidomain) ? $args['src'] : str_replace('img/wiki_up/', "img/wiki_up/$tikidomain/", $args['src']);
148					$image = ['filename' => $m[1], 'where' => 'wiki', 'zip' => "$dir/images/wiki/" . $m[1], 'wiki' => $args['src']];
149					if (! $this->zip->addFile($file, $image['zip'])) {
150						$this->errors[] = 'Can not add the image ';
151						$this->errorsArgs[] = $file;
152						return false;
153					}
154				} elseif (! empty($args['src']) && preg_match('|show_image.php\?(.*)|', $args['src'], $m)) {
155					$imagegallib = TikiLib::lib('imagegal');
156					if (($i = strpos($args['src'], 'tiki-download_file.php')) > 0) {
157						$path = $_SERVER['HTTP_HOST'] . $tikiroot . substr($args['src'], $i);
158					} else {
159						$path = $_SERVER['HTTP_HOST'] . $tikiroot . $args['src'];
160					}
161					$img = $this->httprequest($path);
162					parse_str($m[1], $p);
163
164					if (isset($p['name']) && isset($p['galleryId'])) {
165						$id = $imagegallib->get_imageid_byname($p['name'], $p['galleryId']);
166					} elseif (isset($p['name'])) {
167						$id = $imagegallib->get_imageid_byname($p['name']);
168					} elseif (isset($p['id'])) {
169						$id = $p['id'];
170					}
171
172					$image = ['where' => 'gal', 'zip' => "$dir/images/gal/" . $id, 'wiki' => $args['src']];
173
174					if (! $this->zip->addFromString($image['zip'], $img)) {
175						$this->errors[] = 'Can not add the image';
176						$this->errorsArgs[] = $m[1];
177						return false;
178					}
179				} elseif (! empty($args['src']) && preg_match('|tiki-download_file.php\?(.*)|', $args['src'], $m)) {
180					if (($i = strpos($args['src'], 'tiki-download_file.php')) > 0) {
181						$path = $_SERVER['HTTP_HOST'] . $tikiroot . substr($args['src'], $i);
182					} else {
183						$path = $_SERVER['HTTP_HOST'] . $tikiroot . $args['src'];
184					}
185
186					$img = $this->httprequest($path);
187					parse_str($m[1], $p);
188					$image = ['where' => 'fgal', 'zip' => "$dir/images/fgal/" . $p['fileId'], 'wiki' => $args['src']];
189
190					if (! $this->zip->addFromString($image['zip'], $img)) {
191						$this->errors[] = 'Can not add the image';
192						$this->errorsArgs[] = $m[1];
193						return false;
194					}
195				} /* else no idea where the img comes from - suppose there are outside tw */
196				$images[] = $image;
197			}
198		}
199
200		$smarty->assign_by_ref('images', $images);
201
202		if ($prefs['feature_wiki_attachments'] == 'y' && $this->config['attachments']) {
203			$wikilib = TikiLib::lib('wiki');
204			$attachments = $wikilib->list_wiki_attachments($page, 0, -1);
205			if (! empty($attachments['cant'])) {
206				foreach ($attachments['data'] as $key => $att) {
207					$att_info = $wikilib->get_item_attachment($att['attId']);
208					$attachments['data'][$key]['zip'] = "$dir/attachments/" . $att['attId'];
209					if ($prefs['w_use_dir']) {
210						if (! $this->zip->addFile($prefs['w_use_dir'] . $att_info['path'], $attachments['data'][$key]['zip'])) {
211							$this->errors[] = 'Can not add the attachment';
212							$this->errorsArgs[] = $att_info['attId'];
213							return false;
214						}
215					} else {
216						if (! $this->zip->addFromString($attachments['data'][$key]['zip'], $att_info['data'])) {
217							$this->errors[] = 'Can not add the attachment';
218							$this->errorsArgs[] = $att_info['attId'];
219							return false;
220						}
221					}
222				}
223				$smarty->assign_by_ref('attachments', $attachments['data']);
224			}
225		}
226
227		if ($prefs['feature_history'] == 'y' && $this->config['history']) {
228			$histlib = TikiLib::lib('hist');
229			$history = $histlib->get_page_history($page, false);
230			foreach ($history as $key => $hist) {
231				$all = $histlib->get_version($page, $hist['version']); // can be optimised if returned in the list
232				//$history[$key]['data'] = $all['data'];
233				$history[$key]['zip'] = "$dir/history/" . $all['version'] . '.txt';
234				if (! $this->zip->addFromString($history[$key]['zip'], $all['data'])) {
235					$this->errors[] = 'Can not add the history';
236					$this->errorsArgs[] = $all['version'];
237					return false;
238				}
239			}
240			$smarty->assign_by_ref('history', $history);
241		}
242
243		$smarty->assign_by_ref('config', $this->config);
244		$this->xml .= $smarty->fetch('tiki-export_page_xml.tpl');
245		return true;
246	}
247
248	/* import pages or structure */
249	function import_pages($zipFile = 'dump/xml.zip', $config = null)
250	{
251		if (! empty($config)) {
252			$this->config = array_merge($this->config, $config);
253		}
254
255		if (! ($this->zip = new ZipArchive())) {
256			$this->errors[] = 'Problem zip initialisation';
257			$this->errorsArgs[] = '';
258			return false;
259		}
260
261		if (! $this->zip->open($zipFile)) {
262			$this->errors[] = 'The file cannot be opened';
263			$this->errorsArgs[] = $zipFile;
264			return false;
265		}
266
267		if (($this->xml = $this->zip->getFromName(WIKI_XML)) === false) {
268			$this->errors[] = 'Can not unzip';
269			$this->errorsArgs[] = WIKI_XML;
270			return false;
271		}
272
273		$parser = new page_Parser();
274		$parser->setInput($this->xml);
275		$ok = $parser->parse();
276		if (PEAR::isError($ok)) {
277			$this->errors[] = $ok->getMessage();
278			$this->errorsArgs[] = '';
279			return false;
280		}
281		$infos = $parser->getPages();
282
283		if ($this->config['debug']) {
284			echo 'XML PARSING<pre>';
285			print_r($infos);
286			echo '</pre>';
287		}
288
289		foreach ($infos as $info) {
290			if (! $this->create_page($info)) {
291				return false;
292			}
293		}
294		$this->zip->close();
295		return true;
296	}
297
298	/* create a page from an xml parsing result */
299	function create_page($info)
300	{
301		global $prefs, $tiki_p_wiki_attach_files, $tiki_p_edit_comments, $tikidomain;
302		$tikilib = TikiLib::lib('tiki');
303
304		if (($info['data'] = $this->zip->getFromName($info['zip'])) === false) {
305			$this->errors[] = 'Can not unzip';
306			$this->errorsArgs[] = $info['zip'];
307			return false;
308		}
309
310		if ($this->page_exists($info['name'])) {
311			$old = true;
312			$tikilib->update_page(
313				$info['name'],
314				$info['data'],
315				'Updated from import',
316				! empty($this->config['fromUser']) ? $this->config['fromUser'] : $info['user'],
317				! empty($this->config['fromSite']) ? $this->config['fromSite'] : $info['ip'],
318				$info['description'],
319				0,
320				isset($info['lang']) ? $info['lang'] : '',
321				isset($info['is_html']) ? $info['is_html'] : false,
322				null,
323				null,
324				isset($info['wysiwyg']) ? $info['wysiwyg'] : null
325			);
326		} else {
327			$old = false;
328			$tikilib->create_page(
329				$info['name'],
330				$info['hits'],
331				$info['data'],
332				$info['lastModif'],
333				$info['comment'],
334				! empty($this->config['fromUser']) ? $this->config['fromUser'] : $info['user'],
335				! empty($this->config['fromSite']) ? $this->config['fromSite'] : $info['ip'],
336				$info['description'],
337				isset($info['lang']) ? $info['lang'] : '',
338				isset($info['is_html']) ? $info['is_html'] : false,
339				null,
340				isset($info['wysiwyg']) ? $info['wysiwyg'] : null,
341				'',
342				0,
343				$info['created']
344			);
345		}
346
347		if ($prefs['feature_wiki_comments'] == 'y' && $tiki_p_edit_comments == 'y' && ! empty($info['comments'])) {
348			$newThreadIds = [];
349
350			foreach ($info['comments'] as $comment) {
351				$commentslib = TikiLib::lib('comments');
352				$parentId = empty($comment['parentId']) ? 0 : $newThreadIds[$comment['parentId']];
353				if ($parentId) {
354					$reply_info = $commentslib->get_comment($parentId);
355					$in_reply_to = $reply_info['message_id'];
356				}
357
358				$newThreadIds[$comment['threadId']] = $commentslib->post_new_comment(
359					'wiki page:' . $info['name'],
360					$parentId,
361					$this->config['fromUser'] ? $this->config['fromUser'] : $comment['user'],
362					$comment['title'],
363					$comment['data'],
364					$message_id,
365					$in_reply_to,
366					'n',
367					'',
368					'',
369					'',
370					'',
371					$comment['date']
372				);
373			}
374		}
375
376		if ($prefs['feature_wiki_attachments'] == 'y' && $tiki_p_wiki_attach_files == 'y' && ! empty($info['attachments'])) {
377			foreach ($info['attachments'] as $attachment) {
378				if (($attachment['data'] = $this->zip->getFromName($attachment['zip'])) === false) {
379					$this->errors[] = 'Can not unzip attachment';
380					$this->errorsArgs[] = $attachment['zip'];
381					return false;
382				}
383				if ($prefs['w_use_db'] == 'y') {
384					$fhash = '';
385				} else {
386					$fhash = $this->get_attach_hash_file_name($attachment['filename']);
387					if ($fw = fopen($prefs['w_use_dir'] . $fhash, 'wb')) {
388						if (! fwrite($fw, $attachment['data'])) {
389							$this->errors[] = 'Cannot write to this file';
390							$this->errorsArgs[] = $prefs['w_use_dir'] . $fhash;
391						}
392						fclose($fw);
393						$attachment['data'] = '';
394					} else {
395						$this->errors[] = 'Cannot open this file';
396						$this->errorsArgs[] = $prefs['w_use_dir'] . $fhash;
397					}
398				}
399
400				$wikilib = TikiLib::lib('wiki');
401				$wikilib->wiki_attach_file(
402					$info['name'],
403					$attachment['filename'],
404					$attachment['filetype'],
405					$attachment['filesize'],
406					$attachment['data'],
407					$attachment['comment'],
408					$attachment['user'],
409					$fhash,
410					$attachment['created']
411				);
412			}
413		}
414
415		if ($prefs['feature_wiki_pictures'] == 'y' && ! empty($info['images'])) {
416			foreach ($info['images'] as $image) {
417				if (empty($image['zip'])) {//external link to image
418					continue;
419				}
420				if (($image['data'] = $this->zip->getFromName($image['zip'])) === false) {
421					$this->errors[] = 'Can not unzip image';
422					$this->errorsArgs[] = $image['zip'];
423					return false;
424				}
425				if ($image['where'] == 'wiki') {
426					$wiki_up = 'img/wiki_up/';
427					if ($tikidomain) {
428						$wiki_up .= "$tikidomain/";
429					}
430					$name = str_replace('img/wiki_up/', '', $image['wiki']);
431					file_put_contents($wiki_up . $name, $image['data']);
432					chmod($wiki_up . $name, 0644);
433				}
434			}
435		}
436
437		if ($prefs['feature_history'] == 'y' && ! empty($info['history'])) {
438			$query = 'select max(`version`) from `tiki_history` where `pageName`=?';
439			$maxVersion = $this->getOne($query, [$info['name']]);
440
441			if (! $maxVersion) {
442				$maxVersion = 0;
443			}
444			$newVersion = $maxVersion;
445
446			foreach ($info['history'] as $version) {
447				if (($version['data'] = $this->zip->getFromName($version['zip'])) === false) {
448					$this->errors[] = 'Can not unzip history';
449					$this->errorsArgs[] = $version['version'];
450					return false;
451				}
452				$query = 'insert into `tiki_history`(`pageName`, `version`, `lastModif`, `user`, `ip`, `comment`, `data`, `description`) values(?,?,?,?,?,?,?,?)';
453
454				$this->query(
455					$query,
456					[
457						$info['name'],
458						$version['version'] + $maxVersion,
459						$old ? $tikilib->now : $version['lastModif'],
460						$version['user'],
461						$version['ip'],
462						$version['comment'],
463						$version['data'],
464						$version['description']
465					]
466				);
467
468				$newVersion = max($version['version'] + $maxVersion, $newVersion);
469			}
470			$query = 'update `tiki_pages` set `version`=? where `pageName`=?';
471			$this->query($query, [$newVersion, $info['name']]);
472		}
473
474		if ($prefs['feature_wiki_structure'] == 'y' && ! empty($info['structure'])) {
475			$structlib = TikiLib::lib('struct');
476			//TODO alias
477			if ($info['structure'] == 1) {
478				$this->structureStack[$info['structure']] = $structlib->s_create_page(null, null, $info['name'], '');
479				if (empty($this->structureStack[$info['structure']])) {
480					$this->errors[] = 'A structure already exists';
481					$this->errorsArgs[] = $info['name'];
482					return false;
483				}
484			} elseif (! empty($info['structure'])) {
485				$this->structureStack[$info['structure']] = $structlib->s_create_page(
486					$this->structureStack[$info['structure'] - 1],
487					isset($this->structureStack[$info['structure']]) ? $this->structureStack[$info['structure']] : '',
488					$info['name'],
489					'',
490					$this->structureStack[1]
491				);
492			}
493		}
494		return true;
495	}
496}
497$xmllib = new XmlLib;
498
499class page_Parser extends XML_Parser
500{
501	var $page;
502	var $currentTag = null;
503	var $context = null;
504	var $folding = false; // keep tag as original
505	var $commentsStack = [];
506	var $commentId = 0;
507	var $iStructure = 0;
508
509	function startHandler($parser, $name, $attribs)
510	{
511		switch ($name) {
512			case 'page':
513				$this->context = null;
514				if (is_array($attribs)) {
515					$this->page = [
516									'data' => '',
517									'comment' => '',
518									'description' => '',
519									'user' => 'admin',
520									'ip' => '0.0.0.0',
521									'lang' => '',
522									'is_html' => false,
523									'hash' => null,
524									'wysiwyg' => null
525					];
526					$this->page = array_merge($this->page, $attribs);
527				}
528				if ($this->iStructure > 0) {
529					$this->page['structure'] = $this->iStructure;
530				}
531				break;
532
533			case 'structure':
534				++$this->iStructure;
535				break;
536
537			case 'comments':
538				$comentsStack = [];
539
540			case 'attachments':
541			case 'history':
542			case 'images':
543				$this->context = $name;
544				$this->i = -1;
545				break;
546
547			case 'comment':
548				if ($this->context == 'comments') {
549					++$this->i;
550					$this->page[$this->context][$this->i] = $attribs;
551					$this->page[$this->context][$this->i]['parentId'] = empty($this->commentsStack) ? 0 : $this->commentsStack[count($this->commentsStack) - 1];
552					$this->page[$this->context][$this->i]['threadId'] = ++$this->commentId;
553					array_push($this->commentsStack, $this->commentId);
554				} else {
555					$this->currentTag = $name;
556				}
557				break;
558
559			case 'attachment':
560				++$this->i;
561				$this->page[$this->context][$this->i] = ['comment' => ''];
562				$this->page[$this->context][$this->i] = array_merge($this->page[$this->context][$this->i], $attribs);
563				break;
564
565			case 'version':
566				++$this->i;
567				$this->page[$this->context][$this->i] = ['comment' => '', 'description' => '', 'ip' => '0.0.0.0'];
568				$this->page[$this->context][$this->i] = array_merge($this->page[$this->context][$this->i], $attribs);
569				break;
570
571			case 'image':
572				++$this->i;
573				$this->page[$this->context][$this->i] = $attribs;
574				break;
575
576			default:
577				$this->currentTag = $name;
578				break;
579		}
580	}
581
582	function endHandler($parser, $name)
583	{
584		$this->currentTag = null;
585		switch ($name) {
586			case 'comments':
587			case 'attachements':
588			case 'history':
589			case 'images':
590				$this->context = null;
591				break;
592
593			case 'comment':
594				array_pop($this->commentsStack);
595				break;
596
597			case 'page':
598				$this->pages[] = $this->page;
599				break;
600
601			case 'structure':
602				--$this->iStructure;
603				break;
604		}
605	}
606
607	function cdataHandler($parser, $data)
608	{
609		$data = trim($data);
610		if (empty($data)) {
611			return true;
612		}
613		if (empty($this->context)) {
614			$this->page[$this->currentTag] = $data;
615		} else {
616			$this->page[$this->context][$this->i][$this->currentTag] = $data;
617		}
618	}
619
620	function getPages()
621	{
622		return $this->pages;
623	}
624}
625