5 Copyright 1999,2000,2001,2002,2004,2005,2006,2007 $ThePhpWikiProgrammingTeam
7 This file is part of PhpWiki.
9 PhpWiki is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 PhpWiki is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with PhpWiki; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 require_once("lib/ziplib.php");
25 require_once("lib/Template.php");
28 * ignore fatal errors during dump
30 function _dump_error_handler(&$error) {
31 if ($error->isFatal()) {
32 $error->errno = E_USER_WARNING;
35 return true; // Ignore error
37 if (preg_match('/Plugin/', $error->errstr))
40 // let the message come through: call the remaining handlers:
44 function StartLoadDump(&$request, $title, $html = '')
46 // MockRequest is from the unit testsuite, a faked request. (may be cmd-line)
47 // We are silent on unittests.
48 if (isa($request,'MockRequest'))
50 // FIXME: This is a hack. This really is the worst overall hack in phpwiki.
52 $html->pushContent('%BODY%');
53 $tmpl = Template('html', array('TITLE' => $title,
55 'CONTENT' => $html ? $html : '%BODY%'));
56 echo ereg_replace('%BODY%.*', '', $tmpl->getExpansion($html));
57 $request->chunkOutput();
59 // set marker for sendPageChangeNotification()
60 $request->_deferredPageChangeNotification = array();
63 function EndLoadDump(&$request)
67 if (isa($request,'MockRequest'))
69 $action = $request->getArg('action');
72 case 'zip': $label = _("ZIP files of database"); break;
73 case 'dumpserial': $label = _("Dump to directory"); break;
74 case 'upload': $label = _("Upload File"); break;
75 case 'loadfile': $label = _("Load File"); break;
76 case 'upgrade': $label = _("Upgrade"); break;
78 case 'ziphtml': $label = _("Dump pages as XHTML"); break;
80 if ($label) $label = str_replace(" ","_",$label);
81 if ($action == 'browse') // loading virgin
82 $pagelink = WikiLink(HOME_PAGE);
84 $pagelink = WikiLink(new WikiPageName(_("PhpWikiAdministration"),false,$label));
86 // do deferred sendPageChangeNotification()
87 if (!empty($request->_deferredPageChangeNotification)) {
88 $pages = $all_emails = $all_users = array();
89 foreach ($request->_deferredPageChangeNotification as $p) {
90 list($pagename, $emails, $userids) = $p;
92 $all_emails = array_unique(array_merge($all_emails, $emails));
93 $all_users = array_unique(array_merge($all_users, $userids));
95 $editedby = sprintf(_("Edited by: %s"), $request->_user->getId());
96 $content = "Loaded the following pages:\n" . join("\n", $pages);
97 if (mail(join(',',$all_emails),"[".WIKI_NAME."] "._("LoadDump"),
101 trigger_error(sprintf(_("PageChange Notification of %s sent to %s"),
102 join("\n",$pages), join(',',$all_users)), E_USER_NOTICE);
104 trigger_error(sprintf(_("PageChange Notification Error: Couldn't send %s to %s"),
105 join("\n",$pages), join(',',$all_users)), E_USER_WARNING);
110 unset($request->_deferredPageChangeNotification);
112 PrintXML(HTML::p(HTML::strong(_("Complete."))),
113 HTML::p(fmt("Return to %s", $pagelink)));
114 // Ugly hack to get valid XHTML code
115 if (isa($WikiTheme, 'WikiTheme_gforge')) {
122 echo "</body></html>\n";
126 ////////////////////////////////////////////////////////////////
128 // Functions for dumping.
130 ////////////////////////////////////////////////////////////////
134 * http://www.nacs.uci.edu/indiv/ehood/MIME/2045/rfc2045.html
135 * http://www.faqs.org/rfcs/rfc2045.html
136 * (RFC 1521 has been superceeded by RFC 2045 & others).
138 * Also see http://www.faqs.org/rfcs/rfc2822.html
140 function MailifyPage ($page, $nversions = 1)
142 $current = $page->getCurrentRevision(false);
145 if (STRICT_MAILABLE_PAGEDUMPS) {
146 $from = defined('SERVER_ADMIN') ? SERVER_ADMIN : 'foo@bar';
147 //This is for unix mailbox format: (not RFC (2)822)
148 // $head .= "From $from " . CTime(time()) . "\r\n";
149 $head .= "Subject: " . rawurlencode($page->getName()) . "\r\n";
150 $head .= "From: $from (PhpWiki)\r\n";
151 // RFC 2822 requires only a Date: and originator (From:)
152 // field, however the obsolete standard RFC 822 also
153 // requires a destination field.
154 $head .= "To: $from (PhpWiki)\r\n";
156 $head .= "Date: " . Rfc2822DateTime($current->get('mtime')) . "\r\n";
157 $head .= sprintf("Mime-Version: 1.0 (Produced by PhpWiki %s)\r\n",
160 // This should just be entered by hand (or by script?)
161 // in the actual pgsrc files, since only they should have
163 //$head .= "X-Rcs-Id: \$Id\$\r\n";
165 $iter = $page->getAllRevisions();
167 while ($revision = $iter->next()) {
168 $parts[] = MimeifyPageRevision($page, $revision);
169 if ($nversions > 0 && count($parts) >= $nversions)
172 if (count($parts) > 1)
173 return $head . MimeMultipart($parts);
175 return $head . $parts[0];
179 * Compute filename to used for storing contents of a wiki page.
181 * Basically we do a rawurlencode() which encodes everything except
182 * ASCII alphanumerics and '.', '-', and '_'.
184 * But we also want to encode leading dots to avoid filenames like
185 * '.', and '..'. (Also, there's no point in generating "hidden" file
186 * names, like '.foo'.)
188 * We have to apply a different "/" logic for dumpserial, htmldump and zipdump.
189 * dirs are allowed for zipdump and htmldump, not for dumpserial
192 * @param $pagename string Pagename.
193 * @return string Filename for page.
195 function FilenameForPage ($pagename, $action = false)
197 $enc = rawurlencode($pagename);
200 $action = $request->getArg('action');
202 if ($action != 'dumpserial') { // zip, ziphtml, dumphtml
203 // For every %2F we will need to mkdir -p dirname($pagename)
204 $enc = preg_replace('/%2F/', '/', $enc);
206 $enc = preg_replace('/^\./', '%2E', $enc);
207 $enc = preg_replace('/%20/', ' ', $enc);
212 * The main() function which generates a zip archive of a PhpWiki.
214 * If $include_archive is false, only the current version of each page
215 * is included in the zip file; otherwise all archived versions are
218 function MakeWikiZip (&$request)
220 if ($request->getArg('include') == 'all') {
221 $zipname = WIKI_NAME . _("FullDump") . date('Ymd-Hi') . '.zip';
222 $include_archive = true;
225 $zipname = WIKI_NAME . _("LatestSnapshot") . date('Ymd-Hi') . '.zip';
226 $include_archive = false;
230 $zip = new ZipWriter("Created by PhpWiki " . PHPWIKI_VERSION, $zipname);
232 /* ignore fatals in plugins */
233 if (check_php_version(4,1)) {
234 global $ErrorManager;
235 $ErrorManager->pushErrorHandler(new WikiFunctionCb('_dump_error_handler'));
238 $dbi =& $request->_dbi;
239 $thispage = $request->getArg('pagename'); // for "Return to ..."
240 if ($exclude = $request->getArg('exclude')) { // exclude which pagenames
241 $excludeList = explodePageList($exclude);
243 $excludeList = array();
245 if ($pages = $request->getArg('pages')) { // which pagenames
246 if ($pages == '[]') // current page
248 $page_iter = new WikiDB_Array_PageIterator(explodePageList($pages));
250 $page_iter = $dbi->getAllPages(false,false,false,$excludeList);
252 $request_args = $request->args;
253 $timeout = (! $request->getArg('start_debug')) ? 30 : 240;
255 while ($page = $page_iter->next()) {
256 $request->args = $request_args; // some plugins might change them (esp. on POST)
257 longer_timeout($timeout); // Reset watchdog
259 $current = $page->getCurrentRevision();
260 if ($current->getVersion() == 0)
263 $pagename = $page->getName();
264 $wpn = new WikiPageName($pagename);
265 if (!$wpn->isValid())
267 if (in_array($page->getName(), $excludeList)) {
271 $attrib = array('mtime' => $current->get('mtime'),
273 if ($page->get('locked'))
274 $attrib['write_protected'] = 1;
276 if ($include_archive)
277 $content = MailifyPage($page, 0);
279 $content = MailifyPage($page);
281 $zip->addRegularFile( FilenameForPage($pagename),
285 if (check_php_version(4,1)) {
286 global $ErrorManager;
287 $ErrorManager->popErrorHandler();
291 function DumpToDir (&$request)
293 $directory = $request->getArg('directory');
294 if (empty($directory))
295 $directory = DEFAULT_DUMP_DIR; // See lib/plugin/WikiForm.php:87
296 if (empty($directory))
297 $request->finish(_("You must specify a directory to dump to"));
299 // see if we can access the directory the user wants us to use
300 if (! file_exists($directory)) {
301 if (! mkdir($directory, 0755))
302 $request->finish(fmt("Cannot create directory '%s'", $directory));
304 $html = HTML::p(fmt("Created directory '%s' for the page dump...",
307 $html = HTML::p(fmt("Using directory '%s'", $directory));
310 StartLoadDump($request, _("Dumping Pages"), $html);
312 $dbi =& $request->_dbi;
313 $thispage = $request->getArg('pagename'); // for "Return to ..."
314 if ($exclude = $request->getArg('exclude')) { // exclude which pagenames
315 $excludeList = explodePageList($exclude);
317 $excludeList = array();
319 if ($pages = $request->getArg('pages')) { // which pagenames
320 if ($pages == '[]') // current page
322 $page_iter = new WikiDB_Array_PageIterator(explodePageList($pages));
324 $page_iter = $dbi->getAllPages(false,false,false,$excludeList);
327 $request_args = $request->args;
328 $timeout = (! $request->getArg('start_debug')) ? 30 : 240;
330 while ($page = $page_iter->next()) {
331 $request->args = $request_args; // some plugins might change them (esp. on POST)
332 longer_timeout($timeout); // Reset watchdog
334 $pagename = $page->getName();
335 if (!isa($request,'MockRequest')) {
336 PrintXML(HTML::br(), $pagename, ' ... ');
340 if (in_array($pagename, $excludeList)) {
341 if (!isa($request, 'MockRequest')) {
342 PrintXML(_("Skipped."));
347 $filename = FilenameForPage($pagename);
349 if($page->getName() != $filename) {
350 $msg->pushContent(HTML::small(fmt("saved as %s", $filename)),
354 if ($request->getArg('include') == 'all')
355 $data = MailifyPage($page, 0);
357 $data = MailifyPage($page);
359 if ( !($fd = fopen($directory."/".$filename, "wb")) ) {
360 $msg->pushContent(HTML::strong(fmt("couldn't open file '%s' for writing",
361 "$directory/$filename")));
362 $request->finish($msg);
365 $num = fwrite($fd, $data, strlen($data));
366 $msg->pushContent(HTML::small(fmt("%s bytes written", $num)));
367 if (!isa($request, 'MockRequest')) {
371 assert($num == strlen($data));
375 EndLoadDump($request);
378 function _copyMsg($page, $smallmsg) {
379 if (!isa($GLOBALS['request'], 'MockRequest')) {
380 if ($page) $msg = HTML(HTML::br(), HTML($page), HTML::small($smallmsg));
381 else $msg = HTML::small($smallmsg);
387 function mkdir_p($pathname, $permission = 0777) {
388 $arr = explode("/", $pathname);
390 return mkdir($pathname, $permission);
392 $s = array_shift($arr);
394 foreach ($arr as $p) {
397 $ok = mkdir($curr, $permission);
399 if (!$ok) return FALSE;
405 * Dump all pages as XHTML to a directory, as pagename.html.
406 * Copies all used css files to the directory, all used images to a
407 * "images" subdirectory, and all used buttons to a "images/buttons" subdirectory.
408 * The webserver must have write permissions to these directories.
409 * chown httpd HTML_DUMP_DIR; chmod u+rwx HTML_DUMP_DIR
412 * @param string directory (optional) path to dump to. Default: HTML_DUMP_DIR
413 * @param string pages (optional) Comma-seperated of glob-style pagenames to dump.
414 * Also array of pagenames allowed.
415 * @param string exclude (optional) Comma-seperated of glob-style pagenames to exclude
417 function DumpHtmlToDir (&$request)
420 $directory = $request->getArg('directory');
421 if (empty($directory))
422 $directory = HTML_DUMP_DIR; // See lib/plugin/WikiForm.php:87
423 if (empty($directory))
424 $request->finish(_("You must specify a directory to dump to"));
426 // See if we can access the directory the user wants us to use
427 if (! file_exists($directory)) {
428 if (! mkdir($directory, 0755))
429 $request->finish(fmt("Cannot create directory '%s'", $directory));
431 $html = HTML::p(fmt("Created directory '%s' for the page dump...",
434 $html = HTML::p(fmt("Using directory '%s'", $directory));
436 StartLoadDump($request, _("Dumping Pages"), $html);
437 $thispage = $request->getArg('pagename'); // for "Return to ..."
439 $dbi =& $request->_dbi;
440 if ($exclude = $request->getArg('exclude')) { // exclude which pagenames
441 $excludeList = explodePageList($exclude);
443 $excludeList = array('DebugAuthInfo', 'DebugGroupInfo', 'AuthInfo');
445 if ($pages = $request->getArg('pages')) { // which pagenames
446 if ($pages == '[]') // current page
448 $page_iter = new WikiDB_Array_generic_iter(explodePageList($pages));
449 // not at admin page: dump only the current page
450 } elseif ($thispage != _("PhpWikiAdministration")) {
451 $page_iter = new WikiDB_Array_generic_iter(array($thispage));
453 $page_iter = $dbi->getAllPages(false,false,false,$excludeList);
456 $WikiTheme->DUMP_MODE = 'HTML';
457 _DumpHtmlToDir($directory, $page_iter, $request->getArg('exclude'));
458 $WikiTheme->DUMP_MODE = false;
460 $request->setArg('pagename',$thispage); // Template::_basepage fix
461 EndLoadDump($request);
464 /* Known problem: any plugins or other code which echo()s text will
465 * lead to a corrupted html zip file which may produce the following
466 * errors upon unzipping:
468 * warning [wikihtml.zip]: 2401 extra bytes at beginning or within zipfile
469 * file #58: bad zipfile offset (local header sig): 177561
470 * (attempting to re-compensate)
472 * However, the actual wiki page data should be unaffected.
474 function MakeWikiZipHtml (&$request)
477 if ($request->getArg('zipname')) {
478 $zipname = basename($request->getArg('zipname'));
479 if (!preg_match("/\.zip$/i", $zipname))
481 $request->setArg('zipname', false);
483 $zipname = "wikihtml.zip";
485 $zip = new ZipWriter("Created by PhpWiki " . PHPWIKI_VERSION, $zipname);
486 $dbi =& $request->_dbi;
487 $thispage = $request->getArg('pagename'); // for "Return to ..."
488 if ($pages = $request->getArg('pages')) { // which pagenames
489 if ($pages == '[]') // current page
491 $page_iter = new WikiDB_Array_generic_iter(explodePageList($pages));
493 $page_iter = $dbi->getAllPages(false,false,false,$request->getArg('exclude'));
496 $WikiTheme->DUMP_MODE = 'ZIPHTML';
497 _DumpHtmlToDir($zip, $page_iter, $request->getArg('exclude'));
498 $WikiTheme->DUMP_MODE = false;
502 * Internal html dumper. Used for dumphtml, ziphtml and pdf
504 function _DumpHtmlToDir ($target, $page_iter, $exclude = false)
506 global $WikiTheme, $request;
507 $silent = true; $zip = false; $directory = false;
508 if ($WikiTheme->DUMP_MODE == 'HTML') {
509 $directory = $target;
511 } elseif ($WikiTheme->DUMP_MODE == 'PDFHTML') {
512 $directory = $target;
513 } elseif (is_object($target)) { // $WikiTheme->DUMP_MODE == 'ZIPHTML'
517 $request->_TemplatesProcessed = array();
518 if ($exclude) { // exclude which pagenames
519 $excludeList = explodePageList($exclude);
521 $excludeList = array('DebugAuthInfo', 'DebugGroupInfo', 'AuthInfo');
523 $WikiTheme->VALID_LINKS = array();
524 if ($request->getArg('format')) { // pagelist
525 $page_iter_sav = $page_iter;
526 foreach ($page_iter_sav->asArray() as $handle) {
527 $WikiTheme->VALID_LINKS[] = is_string($handle) ? $handle : $handle->getName();
529 $page_iter_sav->reset();
532 if (defined('HTML_DUMP_SUFFIX'))
533 $WikiTheme->HTML_DUMP_SUFFIX = HTML_DUMP_SUFFIX;
534 $_bodyAttr = @$WikiTheme->_MoreAttr['body'];
535 unset($WikiTheme->_MoreAttr['body']);
537 if (check_php_version(4,1)) {
538 global $ErrorManager;
539 $ErrorManager->pushErrorHandler(new WikiFunctionCb('_dump_error_handler'));
542 // check if the dumped file will be accessible from outside
543 $doc_root = $request->get("DOCUMENT_ROOT");
544 if ($WikiTheme->DUMP_MODE == 'HTML') {
545 $ldir = NormalizeLocalFileName($directory);
546 $wikiroot = NormalizeLocalFileName('');
547 if (string_starts_with($ldir, $doc_root)) {
548 $link_prefix = substr($directory, strlen($doc_root))."/";
549 } elseif (string_starts_with($ldir, $wikiroot)) {
550 $link_prefix = NormalizeWebFileName(substr($directory, strlen($wikiroot)))."/";
554 $prefix = '/'; // . substr($doc_root,0,2); // add drive where apache is installed
556 $link_prefix = "file://".$prefix.$directory."/";
562 $request_args = $request->args;
563 $timeout = (! $request->getArg('start_debug')) ? 60 : 240;
564 $SAVE_RCS_IDS = $GLOBALS['RCS_IDS'];
567 $directory = str_replace("\\", "/", $directory); // no Win95 support.
568 @mkdir("$directory/images");
572 $already_images = array();
574 while ($page = $page_iter->next()) {
575 if (is_string($page)) {
577 $page = $request->_dbi->getPage($pagename);
579 $pagename = $page->getName();
581 if (empty($firstpage)) $firstpage = $pagename;
582 if (array_key_exists($pagename, $already))
584 $already[$pagename] = 1;
585 $current = $page->getCurrentRevision();
586 //if ($current->getVersion() == 0)
589 $request->args = $request_args; // some plugins might change them (esp. on POST)
590 longer_timeout($timeout); // Reset watchdog
593 $attrib = array('mtime' => $current->get('mtime'),
595 if ($page->get('locked'))
596 $attrib['write_protected'] = 1;
597 } elseif (!$silent) {
598 if (!isa($request,'MockRequest')) {
599 PrintXML(HTML::br(), $pagename, ' ... ');
603 if (in_array($pagename, $excludeList)) {
604 if (!$silent and !isa($request,'MockRequest')) {
605 PrintXML(_("Skipped."));
611 if ($WikiTheme->DUMP_MODE == 'PDFHTML')
612 $request->setArg('action', 'pdf'); // to omit cache headers
613 $request->setArg('pagename', $pagename); // Template::_basepage fix
614 $filename = FilenameForPage($pagename) . $WikiTheme->HTML_DUMP_SUFFIX;
615 $args = array('revision' => $current,
616 'CONTENT' => $current->getTransformedContent(),
617 'relative_base' => $relative_base);
618 // For every %2F will need to mkdir -p dirname($pagename)
619 if (preg_match("/(%2F|\/)/", $filename)) {
620 // mkdir -p and set relative base for subdir pages
621 $filename = preg_replace("/%2F/", "/", $filename);
622 $count = substr_count($filename, "/");
623 $dirname = dirname($filename);
625 mkdir_p($directory."/".$dirname);
626 // Fails with "XX / YY", "XX" is created, "XX / YY" cannot be written
627 // if (isWindows()) // interesting Windows bug: cannot mkdir "bla "
628 // Since dumps needs to be copied, we have to disallow this for all platforms.
629 $filename = preg_replace("/ \//", "/", $filename);
630 $relative_base = "../";
632 $relative_base .= "../";
635 $args['relative_base'] = $relative_base;
639 $DUMP_MODE = $WikiTheme->DUMP_MODE;
640 $data = GeneratePageasXML(new Template('browse', $request, $args),
641 $pagename, $current, $args);
642 $WikiTheme->DUMP_MODE = $DUMP_MODE;
644 if (preg_match_all("/<img .*?src=\"(\/.+?)\"/", $data, $m)) {
645 // fix to local relative path for uploaded images, so that pdf will work
646 foreach ($m[1] as $img_file) {
647 $base = basename($img_file);
648 $data = str_replace('src="'.$img_file.'"','src="images/'.$base.'"', $data);
649 if (array_key_exists($img_file, $already_images))
651 $already_images[$img_file] = 1;
652 // resolve src from webdata to file
653 $src = $doc_root . $img_file;
654 if (file_exists($src) and $base) {
656 $target = "$directory/images/$base";
657 if (copy($src, $target)) {
659 _copyMsg($img_file, fmt("... copied to %s", $target));
662 _copyMsg($img_file, fmt("... not copied to %s", $target));
665 $target = "images/$base";
666 $zip->addSrcFile($target, $src);
673 $outfile = $directory."/".$filename;
674 if ( !($fd = fopen($outfile, "wb")) ) {
675 $msg->pushContent(HTML::strong(fmt("couldn't open file '%s' for writing",
677 $request->finish($msg);
679 $len = strlen($data);
680 $num = fwrite($fd, $data, $len);
681 if ($pagename != $filename) {
682 $link = LinkURL($link_prefix.$filename, $filename);
683 $msg->pushContent(HTML::small(_("saved as "), $link, " ... "));
685 $msg->pushContent(HTML::small(fmt("%s bytes written", $num), "\n"));
687 if (!isa($request, 'MockRequest')) {
691 $request->chunkOutput();
693 assert($num == $len);
695 $outfiles[] = $outfile;
697 $zip->addRegularFile($filename, $data, $attrib);
701 $request->_dbi->_cache->invalidate_cache($pagename);
702 unset ($request->_dbi->_cache->_pagedata_cache);
703 unset ($request->_dbi->_cache->_versiondata_cache);
704 unset ($request->_dbi->_cache->_glv_cache);
706 unset ($request->_dbi->_cache->_backend->_page_data);
709 unset($current->_transformedContent);
711 if (!empty($template)) {
712 unset($template->_request);
717 $GLOBALS['RCS_IDS'] = $SAVE_RCS_IDS;
721 $attrib = false; //array('is_ascii' => 0);
722 if (!empty($WikiTheme->dumped_images) and is_array($WikiTheme->dumped_images)) {
723 // @mkdir("$directory/images");
724 foreach ($WikiTheme->dumped_images as $img_file) {
725 if (array_key_exists($img_file, $already_images))
727 $already_images[$img_file] = 1;
729 and ($from = $WikiTheme->_findFile($img_file, true))
733 $target = "$directory/images/".basename($from);
735 copy($WikiTheme->_path . $from, $target);
737 if (copy($WikiTheme->_path . $from, $target)) {
738 _copyMsg($from, fmt("... copied to %s", $target));
740 _copyMsg($from, fmt("... not copied to %s", $target));
744 $target = "images/".basename($from);
745 $zip->addSrcFile($target, $WikiTheme->_path . $from);
747 } elseif (!$silent) {
748 _copyMsg($from, _("... not found"));
753 if (!empty($WikiTheme->dumped_buttons)
754 and is_array($WikiTheme->dumped_buttons))
758 @mkdir("$directory/images/buttons");
759 foreach ($WikiTheme->dumped_buttons as $text => $img_file) {
760 if (array_key_exists($img_file, $already_images))
762 $already_images[$img_file] = 1;
764 and ($from = $WikiTheme->_findFile($img_file, true))
768 $target = "$directory/images/buttons/".basename($from);
770 copy($WikiTheme->_path . $from, $target);
772 if (copy($WikiTheme->_path . $from, $target)) {
773 _copyMsg($from, fmt("... copied to %s", $target));
775 _copyMsg($from, fmt("... not copied to %s", $target));
779 $target = "images/buttons/".basename($from);
780 $zip->addSrcFile($target, $WikiTheme->_path . $from);
782 } elseif (!$silent) {
783 _copyMsg($from, _("... not found"));
787 if (!empty($WikiTheme->dumped_css) and is_array($WikiTheme->dumped_css)) {
788 foreach ($WikiTheme->dumped_css as $css_file) {
789 if (array_key_exists($css_file, $already_images))
791 $already_images[$css_file] = 1;
793 and ($from = $WikiTheme->_findFile(basename($css_file), true))
796 // TODO: fix @import url(main.css);
798 $target = "$directory/" . basename($css_file);
800 copy($WikiTheme->_path . $from, $target);
802 if (copy($WikiTheme->_path . $from, $target)) {
803 _copyMsg($from, fmt("... copied to %s", $target));
805 _copyMsg($from, fmt("... not copied to %s", $target));
809 //$attrib = array('is_ascii' => 0);
810 $target = basename($css_file);
811 $zip->addSrcFile($target, $WikiTheme->_path . $from);
813 } elseif (!$silent) {
814 _copyMsg($from, _("... not found"));
822 if ($WikiTheme->DUMP_MODE == 'PDFHTML') {
823 if (USE_EXTERNAL_HTML2PDF and $outfiles) {
824 $cmd = EXTERNAL_HTML2PDF_PAGELIST.' "'.join('" "', $outfiles).'"';
825 $filename = FilenameForPage($firstpage);
827 $tmpfile = $directory . "/createpdf.bat";
828 $fp = fopen($tmpfile, "wb");
829 fwrite($fp, $cmd . " > $filename.pdf");
832 if (!headers_sent()) {
833 Header('Content-Type: application/pdf');
837 $tmpdir = getUploadFilePath();
838 $s = passthru($cmd . " > $tmpdir/$filename.pdf");
839 $errormsg = "<br />\nGenerated <a href=\"".getUploadDataPath()."$filename.pdf\">Upload:$filename.pdf</a>\n";
844 foreach($outfiles as $f) unlink($f);
847 if (!empty($errormsg)) {
848 $request->discardOutput();
849 $GLOBALS['ErrorManager']->_postponed_errors = array();
853 if (check_php_version(4,1)) {
854 global $ErrorManager;
855 $ErrorManager->popErrorHandler();
858 $WikiTheme->HTML_DUMP_SUFFIX = '';
859 $WikiTheme->DUMP_MODE = false;
860 $WikiTheme->_MoreAttr['body'] = $_bodyAttr;
864 ////////////////////////////////////////////////////////////////
866 // Functions for restoring.
868 ////////////////////////////////////////////////////////////////
870 function SavePage (&$request, &$pageinfo, $source, $filename)
872 static $overwite_all = false;
873 $pagedata = $pageinfo['pagedata']; // Page level meta-data.
874 $versiondata = $pageinfo['versiondata']; // Revision level meta-data.
876 if (empty($pageinfo['pagename'])) {
877 PrintXML(HTML::p(HTML::strong(_("Empty pagename!"))));
881 if (empty($versiondata['author_id']))
882 $versiondata['author_id'] = $versiondata['author'];
884 // remove invalid backend specific chars. utf8 issues mostly
885 $pagename_check = new WikiPagename($pageinfo['pagename']);
886 if (!$pagename_check->isValid()) {
887 PrintXML(HTML::p(HTML::strong(_("Invalid pagename!")." ".$pageinfo['pagename'])));
890 $pagename = $pagename_check->getName();
891 $content = $pageinfo['content'];
893 if ($pagename == _("InterWikiMap"))
894 $content = _tryinsertInterWikiMap($content);
896 $dbi =& $request->_dbi;
897 $page = $dbi->getPage($pagename);
899 // Try to merge if updated pgsrc contents are different. This
900 // whole thing is hackish
902 // TODO: try merge unless:
903 // if (current contents = default contents && pgsrc_version >=
904 // pgsrc_version) then just upgrade this pgsrc
905 $needs_merge = false;
909 if ($request->getArg('merge')) {
912 else if ($request->getArg('overwrite')) {
916 $current = $page->getCurrentRevision();
918 $edit = $request->getArg('edit');
920 if (isset($edit['keep_old'])) {
924 elseif (isset($edit['overwrite'])) {
928 elseif ( $current and (! $current->hasDefaultContents())
929 && ($current->getPackedContent() != $content) )
931 include_once('lib/editpage.php');
932 $request->setArg('pagename', $pagename);
933 $v = $current->getVersion();
934 $request->setArg('revision', $current->getVersion());
935 $p = new LoadFileConflictPageEditor($request);
936 $p->_content = $content;
937 $p->_currentVersion = $v - 1;
938 $p->editPage($saveFailed = true);
939 return; //early return
943 foreach ($pagedata as $key => $value) {
945 $page->set($key, $value);
948 $mesg = HTML::p(array('style' => 'text-indent: 3em;'));
950 $mesg->pushContent(' ', fmt("from %s", $source));
953 //FIXME: This should not happen! (empty vdata, corrupt cache or db)
954 $current = $page->getCurrentRevision();
956 if ($current->getVersion() == 0) {
957 $mesg->pushContent(' - ', _("New page"));
961 if ( (! $current->hasDefaultContents())
962 && ($current->getPackedContent() != $content) ) {
964 $mesg->pushContent(' ',
965 fmt("has edit conflicts - overwriting anyway"));
967 if (substr_count($source, 'pgsrc')) {
968 $versiondata['author'] = _("The PhpWiki programming team");
969 // but leave authorid as userid who loaded the file
973 if (isset($edit['keep_old'])) {
974 $mesg->pushContent(' ', fmt("keep old"));
976 $mesg->pushContent(' ', fmt("has edit conflicts - skipped"));
977 $needs_merge = true; // hackish, to display the buttons
982 else if ($current->getPackedContent() == $content
983 && $current->get('author') == $versiondata['author']) {
984 // The page metadata is already changed, we don't need a new revision.
985 // This was called previously "is identical to current version %d - skipped"
986 // which is wrong, since the pagedata was stored, not skipped.
987 $mesg->pushContent(' ',
988 fmt("content is identical to current version %d - no new revision created",
989 $current->getVersion()));
996 // in case of failures print the culprit:
997 if (!isa($request,'MockRequest')) {
998 PrintXML(HTML::p(WikiLink($pagename))); flush();
1000 $new = $page->save($content, WIKIDB_FORCE_CREATE, $versiondata);
1002 $mesg->pushContent(' ', fmt("- saved to database as version %d",
1003 $new->getVersion()));
1007 // hackish, $source contains needed path+filename
1008 $f = str_replace(sprintf(_("MIME file %s"), ''), '', $f);
1009 $f = str_replace(sprintf(_("Serialized file %s"), ''), '', $f);
1010 $f = str_replace(sprintf(_("plain file %s"), ''), '', $f);
1011 //check if uploaded file? they pass just the content, but the file is gone
1014 $meb = Button(array('action' => 'loadfile',
1018 _("PhpWikiAdministration"),
1020 $owb = Button(array('action' => 'loadfile',
1023 _("Restore Anyway"),
1024 _("PhpWikiAdministration"),
1026 $mesg->pushContent(' ', $meb, " ", $owb);
1027 if (!$overwite_all) {
1028 $args = $request->getArgs();
1029 $args['overwrite'] = 1;
1030 $owb = Button($args,
1032 _("PhpWikiAdministration"),
1034 $mesg->pushContent(HTML::div(array('class' => 'hint'), $owb));
1035 $overwite_all = true;
1038 $mesg->pushContent(HTML::em(_(" Sorry, cannot merge.")));
1042 if (!isa($request,'MockRequest')) {
1044 PrintXML(HTML::p(HTML::em(WikiLink($pagename))), $mesg);
1051 // action=revert (by diff)
1052 function RevertPage (&$request)
1055 $pagename = $request->getArg('pagename');
1056 $version = $request->getArg('version');
1058 PrintXML(HTML::p(fmt("Revert")," ",WikiLink($pagename)),
1059 HTML::p(_("missing required version argument")));
1062 $dbi =& $request->_dbi;
1063 $page = $dbi->getPage($pagename);
1064 $current = $page->getCurrentRevision();
1065 $currversion = $current->getVersion();
1066 if ($currversion == 0) {
1067 $mesg->pushContent(' ', _("no page content"));
1068 PrintXML(HTML::p(fmt("Revert")," ",WikiLink($pagename)),
1073 if ($currversion == $version) {
1074 $mesg->pushContent(' ', _("same version page"));
1075 PrintXML(HTML::p(fmt("Revert")," ",WikiLink($pagename)),
1080 if ($request->getArg('cancel')) {
1081 $mesg->pushContent(' ', _("Cancelled"));
1082 PrintXML(HTML::p(fmt("Revert")," ",WikiLink($pagename)),
1087 if (!$request->getArg('verify')) {
1088 $mesg->pushContent(HTML::br(),
1091 HTML::form(array('action' => $request->getPostURL(),
1092 'method' => 'post'),
1093 HiddenInputs($request->getArgs(), false, array('verify')),
1094 HiddenInputs(array('verify' => 1)),
1095 Button('submit:verify', _("Yes"), 'button'),
1096 HTML::Raw(' '),
1097 Button('submit:cancel', _("Cancel"), 'button')),
1099 $rev = $page->getRevision($version);
1100 $html = HTML(HTML::p(fmt("Revert %s to version $version", WikiLink($pagename))),
1102 $rev->getTransformedContent());
1103 $template = Template('browse',
1104 array('CONTENT' => $html));
1105 GeneratePage($template, $pagename, $rev);
1106 $request->checkValidators();
1110 $rev = $page->getRevision($version);
1111 $content = $rev->getPackedContent();
1112 $versiondata = $rev->_data;
1113 $versiondata['summary'] = sprintf(_("revert to version %d"), $version);
1114 $new = $page->save($content, $currversion + 1, $versiondata);
1117 $pagelink = WikiLink($pagename);
1118 $mesg->pushContent(fmt("Revert: %s", $pagelink),
1119 fmt("- version %d saved to database as version %d",
1120 $version, $new->getVersion()));
1121 // Force browse of current page version.
1122 $request->setArg('version', false);
1123 $template = Template('savepage', array());
1124 $template->replace('CONTENT', $new->getTransformedContent());
1126 GeneratePage($template, $mesg, $new);
1130 function _tryinsertInterWikiMap($content) {
1132 if (strpos($content, "<verbatim>")) {
1133 //$error_html = " The newly loaded pgsrc already contains a verbatim block.";
1136 if (!$goback && !defined('INTERWIKI_MAP_FILE')) {
1137 $error_html = sprintf(" "._("%s: not defined"), "INTERWIKI_MAP_FILE");
1140 $mapfile = FindFile(INTERWIKI_MAP_FILE,1);
1141 if (!$goback && !file_exists($mapfile)) {
1142 $error_html = sprintf(" "._("%s: file not found"), INTERWIKI_MAP_FILE);
1146 if (!empty($error_html))
1147 trigger_error(_("Default InterWiki map file not loaded.")
1148 . $error_html, E_USER_NOTICE);
1152 // if loading from virgin setup do echo, otherwise trigger_error E_USER_NOTICE
1153 if (!isa($GLOBALS['request'], 'MockRequest'))
1154 echo sprintf(_("Loading InterWikiMap from external file %s."), $mapfile),"<br />";
1156 $fd = fopen ($mapfile, "rb");
1157 $data = fread ($fd, filesize($mapfile));
1159 $content = $content . "\n<verbatim>\n$data</verbatim>\n";
1163 function ParseSerializedPage($text, $default_pagename, $user)
1165 if (!preg_match('/^a:\d+:{[si]:\d+/', $text))
1168 $pagehash = unserialize($text);
1170 // Split up pagehash into four parts:
1173 // page-level meta-data
1174 // revision-level meta-data
1176 if (!defined('FLAG_PAGE_LOCKED'))
1177 define('FLAG_PAGE_LOCKED', 1);
1178 $pageinfo = array('pagedata' => array(),
1179 'versiondata' => array());
1181 $pagedata = &$pageinfo['pagedata'];
1182 $versiondata = &$pageinfo['versiondata'];
1184 // Fill in defaults.
1185 if (empty($pagehash['pagename']))
1186 $pagehash['pagename'] = $default_pagename;
1187 if (empty($pagehash['author'])) {
1188 $pagehash['author'] = $user->getId();
1191 foreach ($pagehash as $key => $value) {
1196 $pageinfo[$key] = $value;
1199 $pageinfo[$key] = join("\n", $value);
1202 if (($value & FLAG_PAGE_LOCKED) != 0)
1203 $pagedata['locked'] = 'yes';
1207 $pagedata[$key] = $value;
1211 $pagedata['perm'] = ParseMimeifiedPerm($value);
1213 case 'lastmodified':
1214 $versiondata['mtime'] = $value;
1219 $versiondata[$key] = $value;
1223 if (empty($pagehash['charset']))
1224 $pagehash['charset'] = 'iso-8859-1';
1225 // compare to target charset
1226 if (strtolower($pagehash['charset']) != strtolower($GLOBALS['charset'])) {
1227 $pageinfo['content'] = charset_convert($params['charset'], $GLOBALS['charset'], $pageinfo['content']);
1228 $pageinfo['pagename'] = charset_convert($params['charset'], $GLOBALS['charset'], $pageinfo['pagename']);
1233 function SortByPageVersion ($a, $b) {
1234 return $a['version'] - $b['version'];
1238 * Security alert! We should not allow to import config.ini into our wiki (or from a sister wiki?)
1239 * because the sql passwords are in plaintext there. And the webserver must be able to read it.
1240 * Detected by Santtu Jarvi.
1242 function LoadFile (&$request, $filename, $text = false, $mtime = false)
1244 if (preg_match("/config$/", dirname($filename)) // our or other config
1245 and preg_match("/config.*\.ini/", basename($filename))) // backups and other versions also
1247 trigger_error(sprintf("Refused to load %s", $filename), E_USER_WARNING);
1250 if (!is_string($text)) {
1252 $stat = stat($filename);
1254 $text = implode("", file($filename));
1257 if (! $request->getArg('start_debug')) @set_time_limit(30); // Reset watchdog
1258 else @set_time_limit(240);
1260 // FIXME: basename("filewithnoslashes") seems to return garbage sometimes.
1261 $basename = basename("/dummy/" . $filename);
1264 $mtime = time(); // Last resort.
1266 // DONE: check source - target charset for content and pagename
1267 // but only for pgsrc'ed content, not from the browser.
1269 $default_pagename = rawurldecode($basename);
1270 if ( ($parts = ParseMimeifiedPages($text)) ) {
1271 if (count($parts) > 1)
1272 $overwrite = $request->getArg('overwrite');
1273 usort($parts, 'SortByPageVersion');
1274 foreach ($parts as $pageinfo) {
1276 if (count($parts) > 1)
1277 $request->setArg('overwrite', 1);
1278 SavePage($request, $pageinfo, sprintf(_("MIME file %s"),
1279 $filename), $basename);
1281 if (count($parts) > 1)
1283 $request->setArg('overwrite', $overwrite);
1285 unset($request->_args['overwrite']);
1287 else if ( ($pageinfo = ParseSerializedPage($text, $default_pagename,
1288 $request->getUser())) ) {
1289 SavePage($request, $pageinfo, sprintf(_("Serialized file %s"),
1290 $filename), $basename);
1294 $user = $request->getUser();
1296 $file_charset = 'iso-8859-1';
1297 // compare to target charset
1298 if ($file_charset != strtolower($GLOBALS['charset'])) {
1299 $text = charset_convert($file_charset, $GLOBALS['charset'], $text);
1300 $default_pagename = charset_convert($file_charset, $GLOBALS['charset'], $default_pagename);
1303 // Assume plain text file.
1304 $pageinfo = array('pagename' => $default_pagename,
1305 'pagedata' => array(),
1307 => array('author' => $user->getId()),
1308 'content' => preg_replace('/[ \t\r]*\n/', "\n",
1311 SavePage($request, $pageinfo, sprintf(_("plain file %s"), $filename),
1316 function LoadZip (&$request, $zipfile, $files = false, $exclude = false) {
1317 $zip = new ZipReader($zipfile);
1318 $timeout = (! $request->getArg('start_debug')) ? 20 : 120;
1319 while (list ($fn, $data, $attrib) = $zip->readFile()) {
1320 // FIXME: basename("filewithnoslashes") seems to return
1321 // garbage sometimes.
1322 $fn = basename("/dummy/" . $fn);
1323 if ( ($files && !in_array($fn, $files))
1324 || ($exclude && in_array($fn, $exclude)) ) {
1325 PrintXML(HTML::p(WikiLink($fn)),
1326 HTML::p(_("Skipping")));
1330 longer_timeout($timeout); // longer timeout per page
1331 LoadFile($request, $fn, $data, $attrib['mtime']);
1335 function LoadDir (&$request, $dirname, $files = false, $exclude = false) {
1336 $fileset = new LimitedFileSet($dirname, $files, $exclude);
1338 if (!$files and ($skiplist = $fileset->getSkippedFiles())) {
1339 PrintXML(HTML::p(HTML::strong(_("Skipping"))));
1341 foreach ($skiplist as $file)
1342 $list->pushContent(HTML::li(WikiLink($file)));
1343 PrintXML(HTML::p($list));
1346 // Defer HomePage loading until the end. If anything goes wrong
1347 // the pages can still be loaded again.
1348 $files = $fileset->getFiles();
1349 if (in_array(HOME_PAGE, $files)) {
1350 $files = array_diff($files, array(HOME_PAGE));
1351 $files[] = HOME_PAGE;
1353 $timeout = (! $request->getArg('start_debug')) ? 20 : 120;
1354 foreach ($files as $file) {
1355 longer_timeout($timeout); // longer timeout per page
1356 if (substr($file,-1,1) != '~') // refuse to load backup files
1357 LoadFile($request, "$dirname/$file");
1361 class LimitedFileSet extends FileSet {
1362 function LimitedFileSet($dirname, $_include, $exclude) {
1363 $this->_includefiles = $_include;
1364 $this->_exclude = $exclude;
1365 $this->_skiplist = array();
1366 parent::FileSet($dirname);
1369 function _filenameSelector($fn) {
1370 $incl = &$this->_includefiles;
1371 $excl = &$this->_exclude;
1373 if ( ($incl && !in_array($fn, $incl))
1374 || ($excl && in_array($fn, $excl)) ) {
1375 $this->_skiplist[] = $fn;
1382 function getSkippedFiles () {
1383 return $this->_skiplist;
1388 function IsZipFile ($filename_or_fd)
1390 // See if it looks like zip file
1391 if (is_string($filename_or_fd))
1393 $fd = fopen($filename_or_fd, "rb");
1394 $magic = fread($fd, 4);
1399 $fpos = ftell($filename_or_fd);
1400 $magic = fread($filename_or_fd, 4);
1401 fseek($filename_or_fd, $fpos);
1404 return $magic == ZIP_LOCHEAD_MAGIC || $magic == ZIP_CENTHEAD_MAGIC;
1408 function LoadAny (&$request, $file_or_dir, $files = false, $exclude = false)
1410 // Try urlencoded filename for accented characters.
1411 if (!file_exists($file_or_dir)) {
1412 // Make sure there are slashes first to avoid confusing phps
1413 // with broken dirname or basename functions.
1414 // FIXME: windows uses \ and :
1415 if (is_integer(strpos($file_or_dir, "/"))) {
1416 $newfile = FindFile($file_or_dir, true);
1417 // Panic. urlencoded by the browser (e.g. San%20Diego => San Diego)
1419 $file_or_dir = dirname($file_or_dir) . "/"
1420 . rawurlencode(basename($file_or_dir));
1422 // This is probably just a file.
1423 $file_or_dir = rawurlencode($file_or_dir);
1427 $type = filetype($file_or_dir);
1428 if ($type == 'link') {
1429 // For symbolic links, use stat() to determine
1430 // the type of the underlying file.
1431 list(,,$mode) = stat($file_or_dir);
1432 $type = ($mode >> 12) & 017;
1435 elseif ($type == 004)
1440 $request->finish(fmt("Empty or not existing source. Unable to load: %s", $file_or_dir));
1442 else if ($type == 'dir') {
1443 LoadDir($request, $file_or_dir, $files, $exclude);
1445 else if ($type != 'file' && !preg_match('/^(http|ftp):/', $file_or_dir))
1447 $request->finish(fmt("Bad file type: %s", $type));
1449 else if (IsZipFile($file_or_dir)) {
1450 LoadZip($request, $file_or_dir, $files, $exclude);
1452 else /* if (!$files || in_array(basename($file_or_dir), $files)) */
1454 LoadFile($request, $file_or_dir);
1458 function LoadFileOrDir (&$request)
1460 $source = $request->getArg('source');
1461 $finder = new FileFinder;
1462 $source = $finder->slashifyPath($source);
1463 $page = rawurldecode(basename($source));
1464 StartLoadDump($request, fmt("Loading '%s'",
1465 HTML(dirname($source),
1466 dirname($source) ? "/" : "",
1467 WikiLink($page,'auto'))));
1469 LoadAny($request, $source);
1471 EndLoadDump($request);
1475 * HomePage was not found so first-time install is supposed to run.
1476 * - import all pgsrc pages.
1477 * - Todo: installer interface to edit config/config.ini settings
1478 * - Todo: ask for existing old index.php to convert to config/config.ini
1479 * - Todo: theme-specific pages:
1480 * blog - HomePage, ADMIN_USER/Blogs
1482 function SetupWiki (&$request)
1484 global $GenericPages, $LANG;
1486 //FIXME: This is a hack (err, "interim solution")
1487 // This is a bogo-bogo-login: Login without
1488 // saving login information in session state.
1489 // This avoids logging in the unsuspecting
1490 // visitor as "The PhpWiki programming team".
1492 // This really needs to be cleaned up...
1493 // (I'm working on it.)
1494 $real_user = $request->_user;
1495 if (ENABLE_USER_NEW)
1496 $request->_user = new _BogoUser(_("The PhpWiki programming team"));
1499 $request->_user = new WikiUser($request, _("The PhpWiki programming team"),
1502 StartLoadDump($request, _("Loading up virgin wiki"));
1504 $pgsrc = FindLocalizedFile(WIKI_PGSRC);
1505 $default_pgsrc = FindFile(DEFAULT_WIKI_PGSRC);
1507 $request->setArg('overwrite', true);
1508 if ($default_pgsrc != $pgsrc) {
1509 LoadAny($request, $default_pgsrc, $GenericPages);
1511 $request->setArg('overwrite', false);
1512 LoadAny($request, $pgsrc);
1513 $dbi =& $request->_dbi;
1515 // Ensure that all mandatory pages are loaded
1516 $finder = new FileFinder;
1517 foreach (array_merge(explode(':','Help/OldTextFormattingRules:Help/TextFormattingRules:PhpWikiAdministration'),
1518 $GLOBALS['AllActionPages'],
1519 array(constant('HOME_PAGE'))) as $f)
1521 $page = gettext($f);
1522 $epage = urlencode($page);
1523 if (! $dbi->isWikiPage($page) ) {
1524 // translated version provided?
1525 if ($lf = FindLocalizedFile($pgsrc . $finder->_pathsep . $epage, 1)) {
1526 LoadAny($request, $lf);
1527 } else { // load english version of required action page
1528 LoadAny($request, FindFile(DEFAULT_WIKI_PGSRC . $finder->_pathsep . urlencode($f)));
1532 if (! $dbi->isWikiPage($page)) {
1533 trigger_error(sprintf("Mandatory file %s couldn't be loaded!", $page),
1538 $pagename = _("InterWikiMap");
1539 $map = $dbi->getPage($pagename);
1540 $map->set('locked', true);
1541 PrintXML(HTML::p(HTML::em(WikiLink($pagename)), HTML::strong(" locked")));
1542 EndLoadDump($request);
1545 function LoadPostFile (&$request)
1547 $upload = $request->getUploadedFile('file');
1550 $request->finish(_("No uploaded file to upload?")); // FIXME: more concise message
1553 // Dump http headers.
1554 StartLoadDump($request, sprintf(_("Uploading %s"), $upload->getName()));
1557 $fd = $upload->open();
1559 LoadZip($request, $fd, false, array(_("RecentChanges")));
1561 LoadFile($request, $upload->getName(), $upload->getContents());
1564 EndLoadDump($request);
1571 // c-basic-offset: 4
1572 // c-hanging-comment-ender-p: nil
1573 // indent-tabs-mode: nil