3 * Copyright 1999,2000,2001,2002,2004,2005,2006,2007 $ThePhpWikiProgrammingTeam
4 * Copyright 2008-2010 Marc-Etienne Vargenau, Alcatel-Lucent
6 * This file is part of PhpWiki.
8 * PhpWiki is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * PhpWiki is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License along
19 * with PhpWiki; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 require_once 'lib/ziplib.php';
24 require_once 'lib/Template.php';
27 * ignore fatal errors during dump
29 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 preg_replace('/%BODY%.*/s', '', $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');
73 $label = _("ZIP files of database");
76 $label = _("Dump to directory");
79 $label = _("Upload File");
82 $label = _("Load File");
85 $label = _("Upgrade");
89 $label = _("Dump Pages as XHTML");
92 if ($label) $label = str_replace(" ", "_", $label);
93 if ($action == 'browse') // loading virgin
94 $pagelink = WikiLink(HOME_PAGE);
96 $pagelink = WikiLink(new WikiPageName(_("PhpWikiAdministration"), false, $label));
98 // do deferred sendPageChangeNotification()
99 if (!empty($request->_deferredPageChangeNotification)) {
100 $pages = $all_emails = $all_users = array();
101 foreach ($request->_deferredPageChangeNotification as $p) {
102 list($pagename, $emails, $userids) = $p;
103 $pages[] = $pagename;
104 $all_emails = array_unique(array_merge($all_emails, $emails));
105 $all_users = array_unique(array_merge($all_users, $userids));
107 $editedby = sprintf(_("Edited by: %s"), $request->_user->getId());
108 $content = _("Loaded the following pages:") . "\n" . join("\n", $pages);
109 if (!mail(join(',', $all_emails), "[" . WIKI_NAME . "] " . _("LoadDump"),
110 _("LoadDump") . "\n" .
114 trigger_error(sprintf(_("PageChange Notification Error: Couldn't send %s to %s"),
115 join("\n", $pages), join(',', $all_users)), E_USER_WARNING);
121 unset($request->_deferredPageChangeNotification);
123 PrintXML(HTML::p(HTML::strong(_("Complete."))),
124 HTML::p(fmt("Return to %s", $pagelink)));
125 // Ugly hack to get valid XHTML code
126 if (isa($WikiTheme, 'WikiTheme_fusionforge')) {
131 } elseif (isa($WikiTheme, 'WikiTheme_Sidebar')
132 or isa($WikiTheme, 'WikiTheme_MonoBook')
138 } elseif (isa($WikiTheme, 'WikiTheme_wikilens')) {
143 } elseif (isa($WikiTheme, 'WikiTheme_blog')) {
146 } elseif (isa($WikiTheme, 'WikiTheme_Crao')
147 or isa($WikiTheme, 'WikiTheme_Hawaiian')
148 or isa($WikiTheme, 'WikiTheme_MacOSX')
149 or isa($WikiTheme, 'WikiTheme_shamino_com')
150 or isa($WikiTheme, 'WikiTheme_smaller')
154 echo "</body></html>\n";
157 ////////////////////////////////////////////////////////////////
159 // Functions for dumping.
161 ////////////////////////////////////////////////////////////////
165 * http://www.nacs.uci.edu/indiv/ehood/MIME/2045/rfc2045.html
166 * http://www.faqs.org/rfcs/rfc2045.html
167 * (RFC 1521 has been superceeded by RFC 2045 & others).
169 * Also see http://www.faqs.org/rfcs/rfc2822.html
171 function MailifyPage($page, $nversions = 1)
173 $current = $page->getCurrentRevision(false);
176 if (STRICT_MAILABLE_PAGEDUMPS) {
177 $from = defined('SERVER_ADMIN') ? SERVER_ADMIN : 'foo@bar';
178 //This is for unix mailbox format: (not RFC (2)822)
179 // $head .= "From $from " . CTime(time()) . "\r\n";
180 $head .= "Subject: " . rawurlencode($page->getName()) . "\r\n";
181 $head .= "From: $from (PhpWiki)\r\n";
182 // RFC 2822 requires only a Date: and originator (From:)
183 // field, however the obsolete standard RFC 822 also
184 // requires a destination field.
185 $head .= "To: $from (PhpWiki)\r\n";
187 $head .= "Date: " . Rfc2822DateTime($current->get('mtime')) . "\r\n";
188 $head .= sprintf("Mime-Version: 1.0 (Produced by PhpWiki %s)\r\n",
191 $iter = $page->getAllRevisions();
193 while ($revision = $iter->next()) {
194 $parts[] = MimeifyPageRevision($page, $revision);
195 if ($nversions > 0 && count($parts) >= $nversions)
198 if (count($parts) > 1)
199 return $head . MimeMultipart($parts);
201 return $head . $parts[0];
205 * Compute filename to used for storing contents of a wiki page.
207 * Basically we do a rawurlencode() which encodes everything except
208 * ASCII alphanumerics and '.', '-', and '_'.
210 * But we also want to encode leading dots to avoid filenames like
211 * '.', and '..'. (Also, there's no point in generating "hidden" file
212 * names, like '.foo'.)
214 * We have to apply a different "/" logic for dumpserial, htmldump and zipdump.
215 * dirs are allowed for zipdump and htmldump, not for dumpserial
218 * @param string $pagename Pagename.
219 * @param string $action.
220 * @return string Filename for page.
222 function FilenameForPage($pagename, $action = '')
224 $enc = rawurlencode($pagename);
227 $action = $request->getArg('action');
229 if ($action != 'dumpserial') { // zip, ziphtml, dumphtml
230 // For every %2F we will need to mkdir -p dirname($pagename)
231 $enc = preg_replace('/%2F/', '/', $enc);
233 $enc = preg_replace('/^\./', '%2E', $enc);
234 $enc = preg_replace('/%20/', ' ', $enc);
235 $enc = preg_replace('/\.$/', '%2E', $enc);
240 * The main() function which generates a zip archive of a PhpWiki.
242 * If $include_archive is false, only the current version of each page
243 * is included in the zip file; otherwise all archived versions are
246 function MakeWikiZip(&$request)
248 global $ErrorManager;
249 if ($request->getArg('include') == 'all') {
250 $zipname = WIKI_NAME . _("FullDump") . date('Ymd-Hi') . '.zip';
251 $include_archive = true;
253 $zipname = WIKI_NAME . _("LatestSnapshot") . date('Ymd-Hi') . '.zip';
254 $include_archive = false;
256 $include_empty = false;
257 if ($request->getArg('include') == 'empty') {
258 $include_empty = true;
261 $zip = new ZipWriter("Created by PhpWiki " . PHPWIKI_VERSION, $zipname);
263 /* ignore fatals in plugins */
264 $ErrorManager->pushErrorHandler(new WikiFunctionCb('_dump_error_handler'));
266 $dbi =& $request->_dbi;
267 $thispage = $request->getArg('pagename'); // for "Return to ..."
268 if ($exclude = $request->getArg('exclude')) { // exclude which pagenames
269 $excludeList = explodePageList($exclude);
271 $excludeList = array();
273 if ($pages = $request->getArg('pages')) { // which pagenames
274 if ($pages == '[]') // current page
276 $page_iter = new WikiDB_Array_PageIterator(explodePageList($pages));
278 $page_iter = $dbi->getAllPages(false, false, false, $excludeList);
280 $request_args = $request->args;
281 $timeout = (!$request->getArg('start_debug')) ? 30 : 240;
283 while ($page = $page_iter->next()) {
284 $request->args = $request_args; // some plugins might change them (esp. on POST)
285 longer_timeout($timeout); // Reset watchdog
287 $current = $page->getCurrentRevision();
288 if ($current->getVersion() == 0)
291 $pagename = $page->getName();
292 $wpn = new WikiPageName($pagename);
293 if (!$wpn->isValid())
295 if (in_array($page->getName(), $excludeList)) {
299 $attrib = array('mtime' => $current->get('mtime'),
301 if ($page->get('locked'))
302 $attrib['write_protected'] = 1;
304 if ($include_archive)
305 $content = MailifyPage($page, 0);
307 $content = MailifyPage($page);
309 $zip->addRegularFile(FilenameForPage($pagename),
314 $ErrorManager->popErrorHandler();
317 function DumpToDir(&$request)
319 $directory = $request->getArg('directory');
320 if (empty($directory))
321 $directory = DEFAULT_DUMP_DIR; // See lib/plugin/WikiForm.php:87
322 if (empty($directory))
323 $request->finish(_("You must specify a directory to dump to"));
325 // see if we can access the directory the user wants us to use
326 if (!file_exists($directory)) {
327 if (!mkdir($directory, 0755))
328 $request->finish(fmt("Cannot create directory ā%sā", $directory));
330 $html = HTML::p(fmt("Created directory ā%sā for the page dump...",
333 $html = HTML::p(fmt("Using directory ā%sā", $directory));
336 StartLoadDump($request, _("Dumping Pages"), $html);
338 $dbi =& $request->_dbi;
339 $thispage = $request->getArg('pagename'); // for "Return to ..."
340 if ($exclude = $request->getArg('exclude')) { // exclude which pagenames
341 $excludeList = explodePageList($exclude);
343 $excludeList = array();
345 $include_empty = false;
346 if ($request->getArg('include') == 'empty') {
347 $include_empty = true;
349 if ($pages = $request->getArg('pages')) { // which pagenames
350 if ($pages == '[]') // current page
352 $page_iter = new WikiDB_Array_PageIterator(explodePageList($pages));
354 $page_iter = $dbi->getAllPages($include_empty, false, false, $excludeList);
357 $request_args = $request->args;
358 $timeout = (!$request->getArg('start_debug')) ? 30 : 240;
360 while ($page = $page_iter->next()) {
361 $request->args = $request_args; // some plugins might change them (esp. on POST)
362 longer_timeout($timeout); // Reset watchdog
364 $pagename = $page->getName();
365 if (!isa($request, 'MockRequest')) {
366 PrintXML(HTML::br(), $pagename, ' ... ');
370 if (in_array($pagename, $excludeList)) {
371 if (!isa($request, 'MockRequest')) {
372 PrintXML(_("Skipped."));
377 $filename = FilenameForPage($pagename);
379 if ($page->getName() != $filename) {
380 $msg->pushContent(HTML::small(fmt("saved as %s", $filename)),
384 if ($request->getArg('include') == 'all')
385 $data = MailifyPage($page, 0);
387 $data = MailifyPage($page);
389 if (!($fd = fopen($directory . "/" . $filename, "wb"))) {
390 $msg->pushContent(HTML::strong(fmt("couldn't open file ā%sā for writing",
391 "$directory/$filename")));
392 $request->finish($msg);
395 $num = fwrite($fd, $data, strlen($data));
396 $msg->pushContent(HTML::small(fmt("%s bytes written", $num)));
397 if (!isa($request, 'MockRequest')) {
401 assert($num == strlen($data));
405 EndLoadDump($request);
408 function _copyMsg($page, $smallmsg)
410 if (!isa($GLOBALS['request'], 'MockRequest')) {
411 if ($page) $msg = HTML(HTML::br(), HTML($page), HTML::small($smallmsg));
412 else $msg = HTML::small($smallmsg);
418 function mkdir_p($pathname, $permission = 0777)
420 $arr = explode("/", $pathname);
422 return mkdir($pathname, $permission);
424 $s = array_shift($arr);
426 foreach ($arr as $p) {
429 $ok = mkdir($curr, $permission);
431 if (!$ok) return FALSE;
437 * Dump all pages as XHTML to a directory, as pagename.html.
438 * Copies all used css files to the directory, all used images to a
439 * "images" subdirectory, and all used buttons to a "images/buttons" subdirectory.
440 * The webserver must have write permissions to these directories.
441 * chown httpd HTML_DUMP_DIR; chmod u+rwx HTML_DUMP_DIR
444 * @param string directory (optional) path to dump to. Default: HTML_DUMP_DIR
445 * @param string pages (optional) Comma-separated of glob-style pagenames to dump.
446 * Also array of pagenames allowed.
447 * @param string exclude (optional) Comma-separated of glob-style pagenames to exclude
449 function DumpHtmlToDir(&$request)
452 $directory = $request->getArg('directory');
453 if (empty($directory))
454 $directory = HTML_DUMP_DIR; // See lib/plugin/WikiForm.php:87
455 if (empty($directory))
456 $request->finish(_("You must specify a directory to dump to"));
458 // See if we can access the directory the user wants us to use
459 if (!file_exists($directory)) {
460 if (!mkdir($directory, 0755))
461 $request->finish(fmt("Cannot create directory ā%sā", $directory));
463 $html = HTML::p(fmt("Created directory ā%sā for the page dump...",
466 $html = HTML::p(fmt("Using directory ā%sā", $directory));
468 StartLoadDump($request, _("Dumping Pages"), $html);
469 $thispage = $request->getArg('pagename'); // for "Return to ..."
471 $dbi =& $request->_dbi;
472 if ($exclude = $request->getArg('exclude')) { // exclude which pagenames
473 $excludeList = explodePageList($exclude);
475 $excludeList = array('DebugAuthInfo', 'DebugGroupInfo', 'AuthInfo');
477 if ($pages = $request->getArg('pages')) { // which pagenames
478 if ($pages == '[]') // current page
480 $page_iter = new WikiDB_Array_generic_iter(explodePageList($pages));
481 // not at admin page: dump only the current page
482 } elseif ($thispage != _("PhpWikiAdministration")) {
483 $page_iter = new WikiDB_Array_generic_iter(array($thispage));
485 $page_iter = $dbi->getAllPages(false, false, false, $excludeList);
488 $WikiTheme->DUMP_MODE = 'HTML';
489 _DumpHtmlToDir($directory, $page_iter, $request->getArg('exclude'));
490 $WikiTheme->DUMP_MODE = false;
492 $request->setArg('pagename', $thispage); // Template::_basepage fix
493 EndLoadDump($request);
496 /* Known problem: any plugins or other code which echo()s text will
497 * lead to a corrupted html zip file which may produce the following
498 * errors upon unzipping:
500 * warning [wikihtml.zip]: 2401 extra bytes at beginning or within zipfile
501 * file #58: bad zipfile offset (local header sig): 177561
502 * (attempting to re-compensate)
504 * However, the actual wiki page data should be unaffected.
506 function MakeWikiZipHtml(&$request)
509 if ($request->getArg('zipname')) {
510 $zipname = basename($request->getArg('zipname'));
511 if (!preg_match("/\.zip$/i", $zipname))
513 $request->setArg('zipname', false);
515 $zipname = "wikihtml.zip";
517 $zip = new ZipWriter("Created by PhpWiki " . PHPWIKI_VERSION, $zipname);
518 $dbi =& $request->_dbi;
519 $thispage = $request->getArg('pagename'); // for "Return to ..."
520 if ($pages = $request->getArg('pages')) { // which pagenames
521 if ($pages == '[]') // current page
523 $page_iter = new WikiDB_Array_generic_iter(explodePageList($pages));
525 $page_iter = $dbi->getAllPages(false, false, false, $request->getArg('exclude'));
528 $WikiTheme->DUMP_MODE = 'ZIPHTML';
529 _DumpHtmlToDir($zip, $page_iter, $request->getArg('exclude'));
530 $WikiTheme->DUMP_MODE = false;
534 * Internal html dumper. Used for dumphtml, ziphtml and pdf
536 function _DumpHtmlToDir($target, $page_iter, $exclude = false)
538 global $WikiTheme, $request, $ErrorManager;
542 if ($WikiTheme->DUMP_MODE == 'HTML') {
543 $directory = $target;
545 } elseif ($WikiTheme->DUMP_MODE == 'PDFHTML') {
546 $directory = $target;
547 } elseif (is_object($target)) { // $WikiTheme->DUMP_MODE == 'ZIPHTML'
551 $request->_TemplatesProcessed = array();
552 if ($exclude) { // exclude which pagenames
553 $excludeList = explodePageList($exclude);
555 $excludeList = array('DebugAuthInfo', 'DebugGroupInfo', 'AuthInfo');
557 $WikiTheme->VALID_LINKS = array();
558 if ($request->getArg('format')) { // pagelist
559 $page_iter_sav = $page_iter;
560 foreach ($page_iter_sav->asArray() as $handle) {
561 $WikiTheme->VALID_LINKS[] = is_string($handle) ? $handle : $handle->getName();
563 $page_iter_sav->reset();
566 if (defined('HTML_DUMP_SUFFIX')) {
567 $WikiTheme->HTML_DUMP_SUFFIX = HTML_DUMP_SUFFIX;
569 if (isset($WikiTheme->_MoreAttr['body'])) {
570 $_bodyAttr = $WikiTheme->_MoreAttr['body'];
571 unset($WikiTheme->_MoreAttr['body']);
574 $ErrorManager->pushErrorHandler(new WikiFunctionCb('_dump_error_handler'));
576 // check if the dumped file will be accessible from outside
577 $doc_root = $request->get("DOCUMENT_ROOT");
578 if ($WikiTheme->DUMP_MODE == 'HTML') {
579 $ldir = NormalizeLocalFileName($directory);
580 $wikiroot = NormalizeLocalFileName('');
581 if (string_starts_with($ldir, $doc_root)) {
582 $link_prefix = substr($directory, strlen($doc_root)) . "/";
583 } elseif (string_starts_with($ldir, $wikiroot)) {
584 $link_prefix = NormalizeWebFileName(substr($directory, strlen($wikiroot))) . "/";
588 $prefix = '/'; // . substr($doc_root,0,2); // add drive where apache is installed
590 $link_prefix = "file://" . $prefix . $directory . "/";
596 $request_args = $request->args;
597 $timeout = (!$request->getArg('start_debug')) ? 60 : 240;
600 $directory = str_replace("\\", "/", $directory); // no Win95 support.
601 if (!is_dir("$directory/images"))
602 mkdir("$directory/images");
606 $already_images = array();
608 while ($page = $page_iter->next()) {
609 if (is_string($page)) {
611 $page = $request->_dbi->getPage($pagename);
613 $pagename = $page->getName();
615 if (empty($firstpage)) $firstpage = $pagename;
616 if (array_key_exists($pagename, $already))
618 $already[$pagename] = 1;
619 $current = $page->getCurrentRevision();
620 //if ($current->getVersion() == 0)
623 $request->args = $request_args; // some plugins might change them (esp. on POST)
624 longer_timeout($timeout); // Reset watchdog
627 $attrib = array('mtime' => $current->get('mtime'),
629 if ($page->get('locked'))
630 $attrib['write_protected'] = 1;
631 } elseif (!$silent) {
632 if (!isa($request, 'MockRequest')) {
633 PrintXML(HTML::br(), $pagename, ' ... ');
637 if (in_array($pagename, $excludeList)) {
638 if (!$silent and !isa($request, 'MockRequest')) {
639 PrintXML(_("Skipped."));
645 if ($WikiTheme->DUMP_MODE == 'PDFHTML')
646 $request->setArg('action', 'pdf'); // to omit cache headers
647 $request->setArg('pagename', $pagename); // Template::_basepage fix
648 $filename = FilenameForPage($pagename) . $WikiTheme->HTML_DUMP_SUFFIX;
649 $args = array('revision' => $current,
650 'CONTENT' => $current->getTransformedContent(),
651 'relative_base' => $relative_base);
652 // For every %2F will need to mkdir -p dirname($pagename)
653 if (preg_match("/(%2F|\/)/", $filename)) {
654 // mkdir -p and set relative base for subdir pages
655 $filename = preg_replace("/%2F/", "/", $filename);
656 $count = substr_count($filename, "/");
657 $dirname = dirname($filename);
659 mkdir_p($directory . "/" . $dirname);
660 // Fails with "XX / YY", "XX" is created, "XX / YY" cannot be written
661 // if (isWindows()) // interesting Windows bug: cannot mkdir "bla "
662 // Since dumps needs to be copied, we have to disallow this for all platforms.
663 $filename = preg_replace("/ \//", "/", $filename);
664 $relative_base = "../";
666 $relative_base .= "../";
669 $args['relative_base'] = $relative_base;
673 $DUMP_MODE = $WikiTheme->DUMP_MODE;
674 $data = GeneratePageasXML(new Template('browse', $request, $args),
675 $pagename, $current, $args);
676 $WikiTheme->DUMP_MODE = $DUMP_MODE;
678 if (preg_match_all("/<img .*?src=\"(\/.+?)\"/", $data, $m)) {
679 // fix to local relative path for uploaded images, so that pdf will work
680 foreach ($m[1] as $img_file) {
681 $base = basename($img_file);
682 $data = str_replace('src="' . $img_file . '"', 'src="images/' . $base . '"', $data);
683 if (array_key_exists($img_file, $already_images))
685 $already_images[$img_file] = 1;
686 // resolve src from webdata to file
687 $src = $doc_root . $img_file;
688 if (file_exists($src) and $base) {
690 $target = "$directory/images/$base";
691 if (copy($src, $target)) {
693 _copyMsg($img_file, fmt("... copied to %s", $target));
696 _copyMsg($img_file, fmt("... not copied to %s", $target));
699 $target = "images/$base";
700 $zip->addSrcFile($target, $src);
707 $outfile = $directory . "/" . $filename;
708 if (!($fd = fopen($outfile, "wb"))) {
709 $msg->pushContent(HTML::strong(fmt("couldn't open file ā%sā for writing",
711 $request->finish($msg);
713 $len = strlen($data);
714 $num = fwrite($fd, $data, $len);
715 if ($pagename != $filename) {
716 $link = LinkURL($link_prefix . $filename, $filename);
717 $msg->pushContent(HTML::small(_("saved as "), $link, " ... "));
719 $msg->pushContent(HTML::small(fmt("%s bytes written", $num), "\n"));
721 if (!isa($request, 'MockRequest')) {
725 $request->chunkOutput();
727 assert($num == $len);
729 $outfiles[] = $outfile;
731 $zip->addRegularFile($filename, $data, $attrib);
735 $request->_dbi->_cache->invalidate_cache($pagename);
736 unset ($request->_dbi->_cache->_pagedata_cache);
737 unset ($request->_dbi->_cache->_versiondata_cache);
738 unset ($request->_dbi->_cache->_glv_cache);
740 unset ($request->_dbi->_cache->_backend->_page_data);
743 unset($current->_transformedContent);
745 if (!empty($template)) {
746 unset($template->_request);
753 $attrib = false; //array('is_ascii' => 0);
754 if (!empty($WikiTheme->dumped_images) and is_array($WikiTheme->dumped_images)) {
755 // @mkdir("$directory/images");
756 foreach ($WikiTheme->dumped_images as $img_file) {
757 if (array_key_exists($img_file, $already_images))
759 $already_images[$img_file] = 1;
761 and ($from = $WikiTheme->_findFile($img_file, true))
765 $target = "$directory/images/" . basename($from);
767 copy($WikiTheme->_path . $from, $target);
769 if (copy($WikiTheme->_path . $from, $target)) {
770 _copyMsg($from, fmt("... copied to %s", $target));
772 _copyMsg($from, fmt("... not copied to %s", $target));
776 $target = "images/" . basename($from);
777 $zip->addSrcFile($target, $WikiTheme->_path . $from);
779 } elseif (!$silent) {
780 _copyMsg($from, _("... not found"));
785 if (!empty($WikiTheme->dumped_buttons)
786 and is_array($WikiTheme->dumped_buttons)
789 if ($directory && !is_dir("$directory/images/buttons"))
790 mkdir("$directory/images/buttons");
791 foreach ($WikiTheme->dumped_buttons as $text => $img_file) {
792 if (array_key_exists($img_file, $already_images))
794 $already_images[$img_file] = 1;
796 and ($from = $WikiTheme->_findFile($img_file, true))
800 $target = "$directory/images/buttons/" . basename($from);
802 copy($WikiTheme->_path . $from, $target);
804 if (copy($WikiTheme->_path . $from, $target)) {
805 _copyMsg($from, fmt("... copied to %s", $target));
807 _copyMsg($from, fmt("... not copied to %s", $target));
811 $target = "images/buttons/" . basename($from);
812 $zip->addSrcFile($target, $WikiTheme->_path . $from);
814 } elseif (!$silent) {
815 _copyMsg($from, _("... not found"));
819 if (!empty($WikiTheme->dumped_css) and is_array($WikiTheme->dumped_css)) {
820 foreach ($WikiTheme->dumped_css as $css_file) {
821 if (array_key_exists($css_file, $already_images))
823 $already_images[$css_file] = 1;
825 and ($from = $WikiTheme->_findFile(basename($css_file), true))
829 $target = "$directory/" . basename($css_file);
831 copy($WikiTheme->_path . $from, $target);
833 if (copy($WikiTheme->_path . $from, $target)) {
834 _copyMsg($from, fmt("... copied to %s", $target));
836 _copyMsg($from, fmt("... not copied to %s", $target));
840 //$attrib = array('is_ascii' => 0);
841 $target = basename($css_file);
842 $zip->addSrcFile($target, $WikiTheme->_path . $from);
844 } elseif (!$silent) {
845 _copyMsg($from, _("... not found"));
853 if ($WikiTheme->DUMP_MODE == 'PDFHTML') {
854 if (USE_EXTERNAL_HTML2PDF and $outfiles) {
855 $cmd = EXTERNAL_HTML2PDF_PAGELIST . ' "' . join('" "', $outfiles) . '"';
856 $filename = FilenameForPage($firstpage);
858 $tmpfile = $directory . "/createpdf.bat";
859 $fp = fopen($tmpfile, "wb");
860 fwrite($fp, $cmd . " > $filename.pdf");
863 if (!headers_sent()) {
864 Header('Content-Type: application/pdf');
867 $tmpdir = getUploadFilePath();
868 $s = passthru($cmd . " > $tmpdir/$filename.pdf");
869 $errormsg = "<br />\nGenerated <a href=\"" . getUploadDataPath() . "$filename.pdf\">Upload:$filename.pdf</a>\n";
874 foreach ($outfiles as $f) unlink($f);
877 if (!empty($errormsg)) {
878 $request->discardOutput();
879 $GLOBALS['ErrorManager']->_postponed_errors = array();
883 $ErrorManager->popErrorHandler();
885 $WikiTheme->HTML_DUMP_SUFFIX = '';
886 $WikiTheme->DUMP_MODE = false;
887 $WikiTheme->_MoreAttr['body'] = isset($_bodyAttr) ? $_bodyAttr : '';
890 ////////////////////////////////////////////////////////////////
892 // Functions for restoring.
894 ////////////////////////////////////////////////////////////////
896 function SavePage(&$request, &$pageinfo, $source, $filename)
898 static $overwite_all = false;
899 $pagedata = $pageinfo['pagedata']; // Page level meta-data.
900 $versiondata = $pageinfo['versiondata']; // Revision level meta-data.
902 if (empty($pageinfo['pagename'])) {
903 PrintXML(HTML::p(HTML::strong(_("Empty pagename!"))));
907 if (empty($versiondata['author_id']))
908 $versiondata['author_id'] = $versiondata['author'];
910 // remove invalid backend specific chars. utf8 issues mostly
911 $pagename_check = new WikiPagename($pageinfo['pagename']);
912 if (!$pagename_check->isValid()) {
913 PrintXML(HTML::p(HTML::strong(sprintf(_("ā%sā: Bad page name"), $pageinfo['pagename']))));
916 $pagename = $pagename_check->getName();
917 $content = $pageinfo['content'];
919 if ($pagename == __("InterWikiMap"))
920 $content = _tryinsertInterWikiMap($content);
922 $dbi =& $request->_dbi;
923 $page = $dbi->getPage($pagename);
925 // Try to merge if updated pgsrc contents are different. This
926 // whole thing is hackish
927 $needs_merge = false;
931 if ($request->getArg('merge')) {
933 } elseif ($request->getArg('overwrite')) {
937 $current = $page->getCurrentRevision();
939 $edit = $request->getArg('edit');
941 if (isset($edit['keep_old'])) {
944 } elseif (isset($edit['overwrite'])) {
947 } elseif ($current and (!$current->hasDefaultContents())
948 && ($current->getPackedContent() != $content)
950 include_once 'lib/editpage.php';
951 $request->setArg('pagename', $pagename);
952 $v = $current->getVersion();
953 $request->setArg('revision', $current->getVersion());
954 $p = new LoadFileConflictPageEditor($request);
955 $p->_content = $content;
956 $p->_currentVersion = $v - 1;
957 $p->editPage($saveFailed = true);
958 return; //early return
962 foreach ($pagedata as $key => $value) {
964 $page->set($key, $value);
967 $mesg = HTML::span();
969 $mesg->pushContent(' ', fmt("from ā%sā", $source));
972 //FIXME: This should not happen! (empty vdata, corrupt cache or db)
973 $current = $page->getCurrentRevision();
975 if ($current->getVersion() == 0) {
976 $versiondata['author'] = ADMIN_USER;
977 $versiondata['author_id'] = ADMIN_USER;
978 $mesg->pushContent(' - ', _("New page"));
981 if ((!$current->hasDefaultContents())
982 && ($current->getPackedContent() != $content)
985 $mesg->pushContent(' ',
986 fmt("has edit conflicts - overwriting anyway"));
988 if (substr_count($source, 'pgsrc')) {
989 $versiondata['author'] = ADMIN_USER;
990 // but leave authorid as userid who loaded the file
993 if (isset($edit['keep_old'])) {
994 $mesg->pushContent(' ', fmt("keep old"));
996 $mesg->pushContent(' ', fmt("has edit conflicts - skipped"));
997 $needs_merge = true; // hackish, to display the buttons
1001 } elseif ($current->getPackedContent() == $content) {
1002 // The page content is the same, we don't need a new revision.
1003 $mesg->pushContent(' ',
1004 fmt("content is identical to current version %d - no new revision created",
1005 $current->getVersion()));
1012 // in case of failures print the culprit:
1013 if (!isa($request, 'MockRequest')) {
1014 PrintXML(HTML::span(WikiLink($pagename)));
1017 $new = $page->save($content, WIKIDB_FORCE_CREATE, $versiondata);
1019 $mesg->pushContent(' ', fmt("- saved to database as version %d",
1020 $new->getVersion()));
1021 $mesg->pushContent(HTML::br());
1025 // hackish, $source contains needed path+filename
1026 $f = str_replace(sprintf(_("MIME file %s"), ''), '', $f);
1027 $f = str_replace(sprintf(_("Serialized file %s"), ''), '', $f);
1028 $f = str_replace(sprintf(_("plain file %s"), ''), '', $f);
1029 //check if uploaded file? they pass just the content, but the file is gone
1032 $meb = Button(array('action' => 'loadfile',
1036 _("PhpWikiAdministration"),
1038 $owb = Button(array('action' => 'loadfile',
1039 'overwrite' => true,
1041 _("Restore Anyway"),
1042 _("PhpWikiAdministration"),
1044 $mesg->pushContent(' ', $meb, " ", $owb);
1045 if (!$overwite_all) {
1046 $args = $request->getArgs();
1047 $args['overwrite'] = 1;
1048 $owb = Button($args,
1050 _("PhpWikiAdministration"),
1052 $mesg->pushContent(HTML::span(array('class' => 'hint'), $owb));
1053 $overwite_all = true;
1056 $mesg->pushContent(HTML::em(_(" Sorry, cannot merge.")));
1060 if (!isa($request, 'MockRequest')) {
1062 PrintXML(HTML::em(WikiLink($pagename)), $mesg);
1069 // action=revert (by diff)
1070 function RevertPage(&$request)
1072 $mesg = HTML::div();
1073 $pagename = $request->getArg('pagename');
1074 $version = $request->getArg('version');
1075 $dbi =& $request->_dbi;
1076 $page = $dbi->getPage($pagename);
1078 $request->redirect(WikiURL($page,
1079 array('warningmsg' => _('Revert: missing required version argument'))));
1082 $current = $page->getCurrentRevision();
1083 $currversion = $current->getVersion();
1084 if ($currversion == 0) {
1085 $request->redirect(WikiURL($page,
1086 array('errormsg' => _('No revert: no page content'))));
1089 if ($currversion == $version) {
1090 $request->redirect(WikiURL($page,
1091 array('warningmsg' => _('No revert: same version page'))));
1094 if ($request->getArg('cancel')) {
1095 $request->redirect(WikiURL($page,
1096 array('warningmsg' => _('Revert cancelled'))));
1099 if (!$request->getArg('verify')) {
1100 $mesg->pushContent(HTML::p(fmt("Are you sure to revert %s to version $version?", WikiLink($pagename))),
1101 HTML::form(array('action' => $request->getPostURL(),
1102 'method' => 'post'),
1103 HiddenInputs($request->getArgs(), false, array('verify')),
1104 HiddenInputs(array('verify' => 1)),
1105 Button('submit:verify', _("Yes"), 'button'),
1106 HTML::Raw(' '),
1107 Button('submit:cancel', _("Cancel"), 'button'))
1109 $rev = $page->getRevision($version);
1110 $html = HTML(HTML::fieldset($mesg), HTML::hr(), $rev->getTransformedContent());
1111 $template = Template('browse',
1112 array('CONTENT' => $html));
1113 GeneratePage($template, $pagename, $rev);
1114 $request->checkValidators();
1118 $rev = $page->getRevision($version);
1119 $content = $rev->getPackedContent();
1120 $versiondata = $rev->_data;
1121 $versiondata['summary'] = sprintf(_("Revert to version %d"), $version);
1122 $versiondata['mtime'] = time();
1123 $versiondata['author'] = $request->getUser()->getId();
1124 $new = $page->save($content, $currversion + 1, $versiondata);
1127 $mesg = HTML::span();
1128 $pagelink = WikiLink($pagename);
1129 $mesg->pushContent(fmt("Revert: %s", $pagelink),
1130 fmt("- version %d saved to database as version %d",
1131 $version, $new->getVersion()));
1132 // Force browse of current page version.
1133 $request->setArg('version', false);
1134 $template = Template('savepage', array());
1135 $template->replace('CONTENT', $new->getTransformedContent());
1137 GeneratePage($template, $mesg, $new);
1141 function _tryinsertInterWikiMap($content)
1144 if (strpos($content, "<verbatim>")) {
1145 //$error_html = " The newly loaded pgsrc already contains a verbatim block.";
1148 if (!$goback && !defined('INTERWIKI_MAP_FILE')) {
1149 $error_html = sprintf(" " . _("%s: not defined"), "INTERWIKI_MAP_FILE");
1152 $mapfile = FindFile(INTERWIKI_MAP_FILE, 1);
1153 if (!$goback && !file_exists($mapfile)) {
1154 $error_html = sprintf(" " . _("File ā%sā not found."), INTERWIKI_MAP_FILE);
1158 if (!empty($error_html))
1159 trigger_error(_("Default InterWiki map file not loaded.")
1160 . $error_html, E_USER_NOTICE);
1164 // if loading from virgin setup do echo, otherwise trigger_error E_USER_NOTICE
1165 if (!isa($GLOBALS['request'], 'MockRequest'))
1166 echo sprintf(_("Loading InterWikiMap from external file %s."), $mapfile), "<br />";
1168 $fd = fopen($mapfile, "rb");
1169 $data = fread($fd, filesize($mapfile));
1171 $content = $content . "\n<verbatim>\n$data</verbatim>\n";
1175 function ParseSerializedPage($text, $default_pagename, $user)
1177 if (!preg_match('/^a:\d+:{[si]:\d+/', $text))
1180 $pagehash = unserialize($text);
1182 // Split up pagehash into four parts:
1185 // page-level meta-data
1186 // revision-level meta-data
1188 if (!defined('FLAG_PAGE_LOCKED'))
1189 define('FLAG_PAGE_LOCKED', 1);
1190 if (!defined('FLAG_PAGE_EXTERNAL'))
1191 define('FLAG_PAGE_EXTERNAL', 1);
1192 $pageinfo = array('pagedata' => array(),
1193 'versiondata' => array());
1195 $pagedata = &$pageinfo['pagedata'];
1196 $versiondata = &$pageinfo['versiondata'];
1198 // Fill in defaults.
1199 if (empty($pagehash['pagename']))
1200 $pagehash['pagename'] = $default_pagename;
1201 if (empty($pagehash['author'])) {
1202 $pagehash['author'] = $user->getId();
1205 foreach ($pagehash as $key => $value) {
1210 $pageinfo[$key] = $value;
1213 $pageinfo[$key] = join("\n", $value);
1216 if (($value & FLAG_PAGE_LOCKED) != 0)
1217 $pagedata['locked'] = 'yes';
1218 if (($value & FLAG_PAGE_EXTERNAL) != 0)
1219 $pagedata['external'] = 'yes';
1223 $pagedata[$key] = $value;
1227 $pagedata['perm'] = ParseMimeifiedPerm($value);
1229 case 'lastmodified':
1230 $versiondata['mtime'] = $value;
1235 $versiondata[$key] = $value;
1242 function SortByPageVersion($a, $b)
1244 return $a['version'] - $b['version'];
1248 * Security alert! We should not allow to import config.ini into our wiki (or from a sister wiki?)
1249 * because the sql passwords are in plaintext there. And the webserver must be able to read it.
1250 * Detected by Santtu Jarvi.
1252 function LoadFile(&$request, $filename, $text = false, $mtime = false)
1254 if (preg_match("/config$/", dirname($filename)) // our or other config
1255 and preg_match("/config.*\.ini/", basename($filename))
1256 ) // backups and other versions also
1258 trigger_error(sprintf("Refused to load %s", $filename), E_USER_WARNING);
1261 if (!is_string($text)) {
1263 $stat = stat($filename);
1265 $text = implode("", file($filename));
1268 if (!$request->getArg('start_debug')) @set_time_limit(30); // Reset watchdog
1269 else @set_time_limit(240);
1271 // FIXME: basename("filewithnoslashes") seems to return garbage sometimes.
1272 $basename = basename("/dummy/" . $filename);
1275 $mtime = time(); // Last resort.
1277 $default_pagename = rawurldecode($basename);
1278 if (($parts = ParseMimeifiedPages($text))) {
1279 if (count($parts) > 1)
1280 $overwrite = $request->getArg('overwrite');
1281 usort($parts, 'SortByPageVersion');
1282 foreach ($parts as $pageinfo) {
1284 if (count($parts) > 1)
1285 $request->setArg('overwrite', 1);
1286 SavePage($request, $pageinfo, sprintf(_("MIME file %s"),
1287 $filename), $basename);
1289 if (count($parts) > 1)
1291 $request->setArg('overwrite', $overwrite);
1293 unset($request->_args['overwrite']);
1294 } elseif (($pageinfo = ParseSerializedPage($text, $default_pagename,
1295 $request->getUser()))
1297 SavePage($request, $pageinfo, sprintf(_("Serialized file %s"),
1298 $filename), $basename);
1301 $user = $request->getUser();
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)
1318 $zip = new ZipReader($zipfile);
1319 $timeout = (!$request->getArg('start_debug')) ? 20 : 120;
1320 while (list ($fn, $data, $attrib) = $zip->readFile()) {
1321 // FIXME: basename("filewithnoslashes") seems to return
1322 // garbage sometimes.
1323 $fn = basename("/dummy/" . $fn);
1324 if (($files && !in_array($fn, $files))
1325 || ($exclude && in_array($fn, $exclude))
1327 PrintXML(HTML::p(WikiLink($fn)),
1328 HTML::p(_("Skipping")));
1332 longer_timeout($timeout); // longer timeout per page
1333 LoadFile($request, $fn, $data, $attrib['mtime']);
1337 function LoadDir(&$request, $dirname, $files = false, $exclude = false)
1339 $fileset = new LimitedFileSet($dirname, $files, $exclude);
1341 if (!$files and ($skiplist = $fileset->getSkippedFiles())) {
1342 PrintXML(HTML::p(HTML::strong(_("Skipping"))));
1344 foreach ($skiplist as $file)
1345 $list->pushContent(HTML::li(WikiLink($file)));
1346 PrintXML(HTML::p($list));
1349 // Defer HomePage loading until the end. If anything goes wrong
1350 // the pages can still be loaded again.
1351 $files = $fileset->getFiles();
1352 if (in_array(HOME_PAGE, $files)) {
1353 $files = array_diff($files, array(HOME_PAGE));
1354 $files[] = HOME_PAGE;
1356 $timeout = (!$request->getArg('start_debug')) ? 20 : 120;
1357 foreach ($files as $file) {
1358 longer_timeout($timeout); // longer timeout per page
1359 if (substr($file, -1, 1) != '~') // refuse to load backup files
1360 LoadFile($request, "$dirname/$file");
1364 class LimitedFileSet extends FileSet
1366 function __construct($dirname, $_include, $exclude)
1368 $this->_includefiles = $_include;
1369 $this->_exclude = $exclude;
1370 $this->_skiplist = array();
1371 parent::FileSet($dirname);
1374 function _filenameSelector($fn)
1376 $incl = &$this->_includefiles;
1377 $excl = &$this->_exclude;
1379 if (($incl && !in_array($fn, $incl))
1380 || ($excl && in_array($fn, $excl))
1382 $this->_skiplist[] = $fn;
1389 function getSkippedFiles()
1391 return $this->_skiplist;
1395 function IsZipFile($filename_or_fd)
1397 // See if it looks like zip file
1398 if (is_string($filename_or_fd)) {
1399 $fd = fopen($filename_or_fd, "rb");
1400 $magic = fread($fd, 4);
1403 $fpos = ftell($filename_or_fd);
1404 $magic = fread($filename_or_fd, 4);
1405 fseek($filename_or_fd, $fpos);
1408 return $magic == ZIP_LOCHEAD_MAGIC || $magic == ZIP_CENTHEAD_MAGIC;
1411 function LoadAny(&$request, $file_or_dir, $files = false, $exclude = false)
1413 // Try urlencoded filename for accented characters.
1414 if (!file_exists($file_or_dir)) {
1415 // Make sure there are slashes first to avoid confusing phps
1416 // with broken dirname or basename functions.
1417 // FIXME: windows uses \ and :
1418 if (is_integer(strpos($file_or_dir, "/"))) {
1419 $newfile = FindFile($file_or_dir, true);
1420 // Panic. urlencoded by the browser (e.g. San%20Diego => San Diego)
1422 $file_or_dir = dirname($file_or_dir) . "/"
1423 . rawurlencode(basename($file_or_dir));
1425 // This is probably just a file.
1426 $file_or_dir = rawurlencode($file_or_dir);
1430 $type = filetype($file_or_dir);
1431 if ($type == 'link') {
1432 // For symbolic links, use stat() to determine
1433 // the type of the underlying file.
1434 list(, , $mode) = stat($file_or_dir);
1435 $type = ($mode >> 12) & 017;
1438 elseif ($type == 004)
1443 $request->finish(fmt("Empty or not existing source. Unable to load: %s", $file_or_dir));
1444 } elseif ($type == 'dir') {
1445 LoadDir($request, $file_or_dir, $files, $exclude);
1446 } elseif ($type != 'file' && !preg_match('/^(http|ftp):/', $file_or_dir)) {
1447 $request->finish(fmt("Bad file type: %s", $type));
1448 } elseif (IsZipFile($file_or_dir)) {
1449 LoadZip($request, $file_or_dir, $files, $exclude);
1450 } else /* if (!$files || in_array(basename($file_or_dir), $files)) */ {
1451 LoadFile($request, $file_or_dir);
1455 function LoadFileOrDir(&$request)
1457 $source = $request->getArg('source');
1458 $finder = new FileFinder;
1459 $source = $finder->slashifyPath($source);
1460 StartLoadDump($request,
1461 sprintf(_("Loading ā%sā"), $source));
1462 LoadAny($request, $source);
1463 EndLoadDump($request);
1467 * HomePage was not found so first-time install is supposed to run.
1468 * - import all pgsrc pages.
1469 * - Todo: installer interface to edit config/config.ini settings
1470 * - Todo: ask for existing old index.php to convert to config/config.ini
1471 * - Todo: theme-specific pages:
1472 * blog - HomePage, ADMIN_USER/Blogs
1474 function SetupWiki(&$request)
1476 global $GenericPages, $LANG;
1478 //FIXME: This is a hack (err, "interim solution")
1479 // This is a bogo-bogo-login: Login without
1480 // saving login information in session state.
1481 // This avoids logging in the unsuspecting
1482 // visitor as ADMIN_USER
1484 // This really needs to be cleaned up...
1485 // (I'm working on it.)
1486 $real_user = $request->_user;
1487 $request->_user = new _BogoUser(ADMIN_USER);
1489 StartLoadDump($request, _("Loading up virgin wiki"));
1491 $pgsrc = FindLocalizedFile(WIKI_PGSRC);
1492 $default_pgsrc = FindFile(DEFAULT_WIKI_PGSRC);
1494 $request->setArg('overwrite', true);
1495 if ($default_pgsrc != $pgsrc) {
1496 LoadAny($request, $default_pgsrc, $GenericPages);
1498 $request->setArg('overwrite', false);
1499 LoadAny($request, $pgsrc);
1500 $dbi =& $request->_dbi;
1502 // Ensure that all mandatory pages are loaded
1503 $finder = new FileFinder;
1505 $mandatory = array('SandBox',
1506 'Template/Category',
1510 'CategoryActionPage',
1511 'PhpWikiAdministration');
1513 if ((defined('FUSIONFORGE') and FUSIONFORGE)) {
1514 $mandatory[] = 'Template/UserPage';
1516 $mandatory[] = 'Help/TextFormattingRules';
1519 $mandatory = array_merge($mandatory, $GLOBALS['AllActionPages']);
1520 $mandatory[] = constant('HOME_PAGE');
1522 foreach ($mandatory as $f) {
1523 $page = gettext($f);
1524 $epage = urlencode($page);
1525 if (!$dbi->isWikiPage($page)) {
1526 // translated version provided?
1527 if ($lf = FindLocalizedFile($pgsrc . $finder->_pathsep . $epage, 1)) {
1528 LoadAny($request, $lf);
1529 } else { // load english version of required action page
1530 LoadAny($request, FindFile(DEFAULT_WIKI_PGSRC . $finder->_pathsep . urlencode($f)));
1534 if (!$dbi->isWikiPage($page)) {
1535 trigger_error(sprintf("Mandatory file %s couldn't be loaded!", $page),
1540 $pagename = __("InterWikiMap");
1541 $map = $dbi->getPage($pagename);
1542 $map->set('locked', true);
1543 PrintXML(HTML::p(HTML::em(WikiLink($pagename)), HTML::strong(" locked")));
1544 EndLoadDump($request);
1547 function LoadPostFile(&$request)
1549 $upload = $request->getUploadedFile('file');
1552 $request->finish(_("No uploaded file to upload?")); // FIXME: more concise message
1554 // Dump http headers.
1555 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());
1563 EndLoadDump($request);
1569 // c-basic-offset: 4
1570 // c-hanging-comment-ender-p: nil
1571 // indent-tabs-mode: nil