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