]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/loadsave.php
Reformat code
[SourceForge/phpwiki.git] / lib / loadsave.php
1 <?php
2 /*
3  * Copyright 1999,2000,2001,2002,2004,2005,2006,2007 $ThePhpWikiProgrammingTeam
4  * Copyright 2008-2010 Marc-Etienne Vargenau, Alcatel-Lucent
5  *
6  * This file is part of PhpWiki.
7  *
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.
12  *
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.
17  *
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.
21  */
22
23 require_once 'lib/ziplib.php';
24 require_once 'lib/Template.php';
25
26 /**
27  * ignore fatal errors during dump
28  */
29 function _dump_error_handler($error)
30 {
31     if ($error->isFatal()) {
32         $error->errno = E_USER_WARNING;
33         return true;
34     }
35     return true; // Ignore error
36     /*
37     if (preg_match('/Plugin/', $error->errstr))
38         return true;
39     */
40     // let the message come through: call the remaining handlers:
41     // return false;
42 }
43
44 function StartLoadDump(&$request, $title, $html = '')
45 {
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'))
49         return;
50     // FIXME: This is a hack. This really is the worst overall hack in phpwiki.
51     if ($html)
52         $html->pushContent('%BODY%');
53     $tmpl = Template('html', array('TITLE' => $title,
54         'HEADER' => $title,
55         'CONTENT' => $html ? $html : '%BODY%'));
56     echo preg_replace('/%BODY%.*/s', '', $tmpl->getExpansion($html));
57     $request->chunkOutput();
58
59     // set marker for sendPageChangeNotification()
60     $request->_deferredPageChangeNotification = array();
61 }
62
63 function EndLoadDump(&$request)
64 {
65     global $WikiTheme;
66
67     if (isa($request, 'MockRequest'))
68         return;
69     $action = $request->getArg('action');
70     $label = '';
71     switch ($action) {
72         case 'zip':
73             $label = _("ZIP files of database");
74             break;
75         case 'dumpserial':
76             $label = _("Dump to directory");
77             break;
78         case 'upload':
79             $label = _("Upload File");
80             break;
81         case 'loadfile':
82             $label = _("Load File");
83             break;
84         case 'upgrade':
85             $label = _("Upgrade");
86             break;
87         case 'dumphtml':
88         case 'ziphtml':
89             $label = _("Dump pages as XHTML");
90             break;
91     }
92     if ($label) $label = str_replace(" ", "_", $label);
93     if ($action == 'browse') // loading virgin
94         $pagelink = WikiLink(HOME_PAGE);
95     else
96         $pagelink = WikiLink(new WikiPageName(_("PhpWikiAdministration"), false, $label));
97
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));
106         }
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" .
111                 $editedby . "\n\n" .
112                 $content)
113         )
114             trigger_error(sprintf(_("PageChange Notification of %s sent to %s"),
115                 join("\n", $pages), join(',', $all_users)), E_USER_NOTICE);
116         else
117             trigger_error(sprintf(_("PageChange Notification Error: Couldn't send %s to %s"),
118                 join("\n", $pages), join(',', $all_users)), E_USER_WARNING);
119         unset($pages);
120         unset($all_emails);
121         unset($all_users);
122     }
123     unset($request->_deferredPageChangeNotification);
124
125     PrintXML(HTML::p(HTML::strong(_("Complete."))),
126         HTML::p(fmt("Return to %s", $pagelink)));
127     // Ugly hack to get valid XHTML code
128     if (isa($WikiTheme, 'WikiTheme_fusionforge')) {
129         echo "</div>\n";
130         echo "</div>\n";
131         echo "</article>\n";
132         echo "</div>\n";
133     } elseif (isa($WikiTheme, 'WikiTheme_Sidebar')
134         or isa($WikiTheme, 'WikiTheme_MonoBook')
135     ) {
136         echo "</div>\n";
137         echo "</div>\n";
138         echo "</div>\n";
139         echo "</div>\n";
140     } elseif (isa($WikiTheme, 'WikiTheme_wikilens')) {
141         echo "</div>\n";
142         echo "</td>\n";
143         echo "</tr>\n";
144         echo "</table>\n";
145     } elseif (isa($WikiTheme, 'WikiTheme_blog')) {
146         echo "</div>\n";
147         echo "</div>\n";
148     } elseif (isa($WikiTheme, 'WikiTheme_Crao')
149         or isa($WikiTheme, 'WikiTheme_Hawaiian')
150         or isa($WikiTheme, 'WikiTheme_MacOSX')
151         or isa($WikiTheme, 'WikiTheme_shamino_com')
152         or isa($WikiTheme, 'WikiTheme_smaller')
153     ) {
154         echo "</div>\n";
155     }
156     echo "</body></html>\n";
157 }
158
159 ////////////////////////////////////////////////////////////////
160 //
161 //  Functions for dumping.
162 //
163 ////////////////////////////////////////////////////////////////
164
165 /**
166  * For reference see:
167  * http://www.nacs.uci.edu/indiv/ehood/MIME/2045/rfc2045.html
168  * http://www.faqs.org/rfcs/rfc2045.html
169  * (RFC 1521 has been superceeded by RFC 2045 & others).
170  *
171  * Also see http://www.faqs.org/rfcs/rfc2822.html
172  */
173 function MailifyPage($page, $nversions = 1)
174 {
175     $current = $page->getCurrentRevision(false);
176     $head = '';
177
178     if (STRICT_MAILABLE_PAGEDUMPS) {
179         $from = defined('SERVER_ADMIN') ? SERVER_ADMIN : 'foo@bar';
180         //This is for unix mailbox format: (not RFC (2)822)
181         // $head .= "From $from  " . CTime(time()) . "\r\n";
182         $head .= "Subject: " . rawurlencode($page->getName()) . "\r\n";
183         $head .= "From: $from (PhpWiki)\r\n";
184         // RFC 2822 requires only a Date: and originator (From:)
185         // field, however the obsolete standard RFC 822 also
186         // requires a destination field.
187         $head .= "To: $from (PhpWiki)\r\n";
188     }
189     $head .= "Date: " . Rfc2822DateTime($current->get('mtime')) . "\r\n";
190     $head .= sprintf("Mime-Version: 1.0 (Produced by PhpWiki %s)\r\n",
191         PHPWIKI_VERSION);
192
193     $iter = $page->getAllRevisions();
194     $parts = array();
195     while ($revision = $iter->next()) {
196         $parts[] = MimeifyPageRevision($page, $revision);
197         if ($nversions > 0 && count($parts) >= $nversions)
198             break;
199     }
200     if (count($parts) > 1)
201         return $head . MimeMultipart($parts);
202     assert($parts);
203     return $head . $parts[0];
204 }
205
206 /***
207  * Compute filename to used for storing contents of a wiki page.
208  *
209  * Basically we do a rawurlencode() which encodes everything except
210  * ASCII alphanumerics and '.', '-', and '_'.
211  *
212  * But we also want to encode leading dots to avoid filenames like
213  * '.', and '..'. (Also, there's no point in generating "hidden" file
214  * names, like '.foo'.)
215  *
216  * We have to apply a different "/" logic for dumpserial, htmldump and zipdump.
217  * dirs are allowed for zipdump and htmldump, not for dumpserial
218  *
219  *
220  * @param $pagename string Pagename.
221  * @return string Filename for page.
222  */
223 function FilenameForPage($pagename, $action = false)
224 {
225     $enc = rawurlencode($pagename);
226     if (!$action) {
227         global $request;
228         $action = $request->getArg('action');
229     }
230     if ($action != 'dumpserial') { // zip, ziphtml, dumphtml
231         // For every %2F we will need to mkdir -p dirname($pagename)
232         $enc = preg_replace('/%2F/', '/', $enc);
233     }
234     $enc = preg_replace('/^\./', '%2E', $enc);
235     $enc = preg_replace('/%20/', ' ', $enc);
236     $enc = preg_replace('/\.$/', '%2E', $enc);
237     return $enc;
238 }
239
240 /**
241  * The main() function which generates a zip archive of a PhpWiki.
242  *
243  * If $include_archive is false, only the current version of each page
244  * is included in the zip file; otherwise all archived versions are
245  * included as well.
246  */
247 function MakeWikiZip(&$request)
248 {
249     global $ErrorManager;
250     if ($request->getArg('include') == 'all') {
251         $zipname = WIKI_NAME . _("FullDump") . date('Ymd-Hi') . '.zip';
252         $include_archive = true;
253     } else {
254         $zipname = WIKI_NAME . _("LatestSnapshot") . date('Ymd-Hi') . '.zip';
255         $include_archive = false;
256     }
257     $include_empty = false;
258     if ($request->getArg('include') == 'empty') {
259         $include_empty = true;
260     }
261
262     $zip = new ZipWriter("Created by PhpWiki " . PHPWIKI_VERSION, $zipname);
263
264     /* ignore fatals in plugins */
265     $ErrorManager->pushErrorHandler(new WikiFunctionCb('_dump_error_handler'));
266
267     $dbi =& $request->_dbi;
268     $thispage = $request->getArg('pagename'); // for "Return to ..."
269     if ($exclude = $request->getArg('exclude')) { // exclude which pagenames
270         $excludeList = explodePageList($exclude);
271     } else {
272         $excludeList = array();
273     }
274     if ($pages = $request->getArg('pages')) { // which pagenames
275         if ($pages == '[]') // current page
276             $pages = $thispage;
277         $page_iter = new WikiDB_Array_PageIterator(explodePageList($pages));
278     } else {
279         $page_iter = $dbi->getAllPages(false, false, false, $excludeList);
280     }
281     $request_args = $request->args;
282     $timeout = (!$request->getArg('start_debug')) ? 30 : 240;
283
284     while ($page = $page_iter->next()) {
285         $request->args = $request_args; // some plugins might change them (esp. on POST)
286         longer_timeout($timeout); // Reset watchdog
287
288         $current = $page->getCurrentRevision();
289         if ($current->getVersion() == 0)
290             continue;
291
292         $pagename = $page->getName();
293         $wpn = new WikiPageName($pagename);
294         if (!$wpn->isValid())
295             continue;
296         if (in_array($page->getName(), $excludeList)) {
297             continue;
298         }
299
300         $attrib = array('mtime' => $current->get('mtime'),
301             'is_ascii' => 1);
302         if ($page->get('locked'))
303             $attrib['write_protected'] = 1;
304
305         if ($include_archive)
306             $content = MailifyPage($page, 0);
307         else
308             $content = MailifyPage($page);
309
310         $zip->addRegularFile(FilenameForPage($pagename),
311             $content, $attrib);
312     }
313     $zip->finish();
314
315     $ErrorManager->popErrorHandler();
316 }
317
318 function DumpToDir(&$request)
319 {
320     $directory = $request->getArg('directory');
321     if (empty($directory))
322         $directory = DEFAULT_DUMP_DIR; // See lib/plugin/WikiForm.php:87
323     if (empty($directory))
324         $request->finish(_("You must specify a directory to dump to"));
325
326     // see if we can access the directory the user wants us to use
327     if (!file_exists($directory)) {
328         if (!mkdir($directory, 0755))
329             $request->finish(fmt("Cannot create directory '%s'", $directory));
330         else
331             $html = HTML::p(fmt("Created directory '%s' for the page dump...",
332                 $directory));
333     } else {
334         $html = HTML::p(fmt("Using directory '%s'", $directory));
335     }
336
337     StartLoadDump($request, _("Dumping Pages"), $html);
338
339     $dbi =& $request->_dbi;
340     $thispage = $request->getArg('pagename'); // for "Return to ..."
341     if ($exclude = $request->getArg('exclude')) { // exclude which pagenames
342         $excludeList = explodePageList($exclude);
343     } else {
344         $excludeList = array();
345     }
346     $include_empty = false;
347     if ($request->getArg('include') == 'empty') {
348         $include_empty = true;
349     }
350     if ($pages = $request->getArg('pages')) { // which pagenames
351         if ($pages == '[]') // current page
352             $pages = $thispage;
353         $page_iter = new WikiDB_Array_PageIterator(explodePageList($pages));
354     } else {
355         $page_iter = $dbi->getAllPages($include_empty, false, false, $excludeList);
356     }
357
358     $request_args = $request->args;
359     $timeout = (!$request->getArg('start_debug')) ? 30 : 240;
360
361     while ($page = $page_iter->next()) {
362         $request->args = $request_args; // some plugins might change them (esp. on POST)
363         longer_timeout($timeout); // Reset watchdog
364
365         $pagename = $page->getName();
366         if (!isa($request, 'MockRequest')) {
367             PrintXML(HTML::br(), $pagename, ' ... ');
368             flush();
369         }
370
371         if (in_array($pagename, $excludeList)) {
372             if (!isa($request, 'MockRequest')) {
373                 PrintXML(_("Skipped."));
374                 flush();
375             }
376             continue;
377         }
378         $filename = FilenameForPage($pagename);
379         $msg = HTML();
380         if ($page->getName() != $filename) {
381             $msg->pushContent(HTML::small(fmt("saved as %s", $filename)),
382                 " ... ");
383         }
384
385         if ($request->getArg('include') == 'all')
386             $data = MailifyPage($page, 0);
387         else
388             $data = MailifyPage($page);
389
390         if (!($fd = fopen($directory . "/" . $filename, "wb"))) {
391             $msg->pushContent(HTML::strong(fmt("couldn't open file '%s' for writing",
392                 "$directory/$filename")));
393             $request->finish($msg);
394         }
395
396         $num = fwrite($fd, $data, strlen($data));
397         $msg->pushContent(HTML::small(fmt("%s bytes written", $num)));
398         if (!isa($request, 'MockRequest')) {
399             PrintXML($msg);
400             flush();
401         }
402         assert($num == strlen($data));
403         fclose($fd);
404     }
405
406     EndLoadDump($request);
407 }
408
409 function _copyMsg($page, $smallmsg)
410 {
411     if (!isa($GLOBALS['request'], 'MockRequest')) {
412         if ($page) $msg = HTML(HTML::br(), HTML($page), HTML::small($smallmsg));
413         else $msg = HTML::small($smallmsg);
414         PrintXML($msg);
415         flush();
416     }
417 }
418
419 function mkdir_p($pathname, $permission = 0777)
420 {
421     $arr = explode("/", $pathname);
422     if (empty($arr)) {
423         return mkdir($pathname, $permission);
424     }
425     $s = array_shift($arr);
426     $ok = TRUE;
427     foreach ($arr as $p) {
428         $curr = "$s/$p";
429         if (!is_dir($curr))
430             $ok = mkdir($curr, $permission);
431         $s = $curr;
432         if (!$ok) return FALSE;
433     }
434     return TRUE;
435 }
436
437 /**
438  * Dump all pages as XHTML to a directory, as pagename.html.
439  * Copies all used css files to the directory, all used images to a
440  * "images" subdirectory, and all used buttons to a "images/buttons" subdirectory.
441  * The webserver must have write permissions to these directories.
442  *   chown httpd HTML_DUMP_DIR; chmod u+rwx HTML_DUMP_DIR
443  * should be enough.
444  *
445  * @param string directory (optional) path to dump to. Default: HTML_DUMP_DIR
446  * @param string pages     (optional) Comma-seperated of glob-style pagenames to dump.
447  *                                    Also array of pagenames allowed.
448  * @param string exclude   (optional) Comma-seperated of glob-style pagenames to exclude
449  */
450 function DumpHtmlToDir(&$request)
451 {
452     global $WikiTheme;
453     $directory = $request->getArg('directory');
454     if (empty($directory))
455         $directory = HTML_DUMP_DIR; // See lib/plugin/WikiForm.php:87
456     if (empty($directory))
457         $request->finish(_("You must specify a directory to dump to"));
458
459     // See if we can access the directory the user wants us to use
460     if (!file_exists($directory)) {
461         if (!mkdir($directory, 0755))
462             $request->finish(fmt("Cannot create directory '%s'", $directory));
463         else
464             $html = HTML::p(fmt("Created directory '%s' for the page dump...",
465                 $directory));
466     } else {
467         $html = HTML::p(fmt("Using directory '%s'", $directory));
468     }
469     StartLoadDump($request, _("Dumping Pages"), $html);
470     $thispage = $request->getArg('pagename'); // for "Return to ..."
471
472     $dbi =& $request->_dbi;
473     if ($exclude = $request->getArg('exclude')) { // exclude which pagenames
474         $excludeList = explodePageList($exclude);
475     } else {
476         $excludeList = array('DebugAuthInfo', 'DebugGroupInfo', 'AuthInfo');
477     }
478     if ($pages = $request->getArg('pages')) { // which pagenames
479         if ($pages == '[]') // current page
480             $pages = $thispage;
481         $page_iter = new WikiDB_Array_generic_iter(explodePageList($pages));
482         // not at admin page: dump only the current page
483     } elseif ($thispage != _("PhpWikiAdministration")) {
484         $page_iter = new WikiDB_Array_generic_iter(array($thispage));
485     } else {
486         $page_iter = $dbi->getAllPages(false, false, false, $excludeList);
487     }
488
489     $WikiTheme->DUMP_MODE = 'HTML';
490     _DumpHtmlToDir($directory, $page_iter, $request->getArg('exclude'));
491     $WikiTheme->DUMP_MODE = false;
492
493     $request->setArg('pagename', $thispage); // Template::_basepage fix
494     EndLoadDump($request);
495 }
496
497 /* Known problem: any plugins or other code which echo()s text will
498  * lead to a corrupted html zip file which may produce the following
499  * errors upon unzipping:
500  *
501  * warning [wikihtml.zip]:  2401 extra bytes at beginning or within zipfile
502  * file #58:  bad zipfile offset (local header sig):  177561
503  *  (attempting to re-compensate)
504  *
505  * However, the actual wiki page data should be unaffected.
506  */
507 function MakeWikiZipHtml(&$request)
508 {
509     global $WikiTheme;
510     if ($request->getArg('zipname')) {
511         $zipname = basename($request->getArg('zipname'));
512         if (!preg_match("/\.zip$/i", $zipname))
513             $zipname .= ".zip";
514         $request->setArg('zipname', false);
515     } else {
516         $zipname = "wikihtml.zip";
517     }
518     $zip = new ZipWriter("Created by PhpWiki " . PHPWIKI_VERSION, $zipname);
519     $dbi =& $request->_dbi;
520     $thispage = $request->getArg('pagename'); // for "Return to ..."
521     if ($pages = $request->getArg('pages')) { // which pagenames
522         if ($pages == '[]') // current page
523             $pages = $thispage;
524         $page_iter = new WikiDB_Array_generic_iter(explodePageList($pages));
525     } else {
526         $page_iter = $dbi->getAllPages(false, false, false, $request->getArg('exclude'));
527     }
528
529     $WikiTheme->DUMP_MODE = 'ZIPHTML';
530     _DumpHtmlToDir($zip, $page_iter, $request->getArg('exclude'));
531     $WikiTheme->DUMP_MODE = false;
532 }
533
534 /*
535  * Internal html dumper. Used for dumphtml, ziphtml and pdf
536  */
537 function _DumpHtmlToDir($target, $page_iter, $exclude = false)
538 {
539     global $WikiTheme, $request, $ErrorManager;
540     $silent = true;
541     $zip = false;
542     $directory = false;
543     if ($WikiTheme->DUMP_MODE == 'HTML') {
544         $directory = $target;
545         $silent = false;
546     } elseif ($WikiTheme->DUMP_MODE == 'PDFHTML') {
547         $directory = $target;
548     } elseif (is_object($target)) { // $WikiTheme->DUMP_MODE == 'ZIPHTML'
549         $zip = $target;
550     }
551
552     $request->_TemplatesProcessed = array();
553     if ($exclude) { // exclude which pagenames
554         $excludeList = explodePageList($exclude);
555     } else {
556         $excludeList = array('DebugAuthInfo', 'DebugGroupInfo', 'AuthInfo');
557     }
558     $WikiTheme->VALID_LINKS = array();
559     if ($request->getArg('format')) { // pagelist
560         $page_iter_sav = $page_iter;
561         foreach ($page_iter_sav->asArray() as $handle) {
562             $WikiTheme->VALID_LINKS[] = is_string($handle) ? $handle : $handle->getName();
563         }
564         $page_iter_sav->reset();
565     }
566
567     if (defined('HTML_DUMP_SUFFIX')) {
568         $WikiTheme->HTML_DUMP_SUFFIX = HTML_DUMP_SUFFIX;
569     }
570     if (isset($WikiTheme->_MoreAttr['body'])) {
571         $_bodyAttr = $WikiTheme->_MoreAttr['body'];
572         unset($WikiTheme->_MoreAttr['body']);
573     }
574
575     $ErrorManager->pushErrorHandler(new WikiFunctionCb('_dump_error_handler'));
576
577     // check if the dumped file will be accessible from outside
578     $doc_root = $request->get("DOCUMENT_ROOT");
579     if ($WikiTheme->DUMP_MODE == 'HTML') {
580         $ldir = NormalizeLocalFileName($directory);
581         $wikiroot = NormalizeLocalFileName('');
582         if (string_starts_with($ldir, $doc_root)) {
583             $link_prefix = substr($directory, strlen($doc_root)) . "/";
584         } elseif (string_starts_with($ldir, $wikiroot)) {
585             $link_prefix = NormalizeWebFileName(substr($directory, strlen($wikiroot))) . "/";
586         } else {
587             $prefix = '';
588             if (isWindows()) {
589                 $prefix = '/'; // . substr($doc_root,0,2); // add drive where apache is installed
590             }
591             $link_prefix = "file://" . $prefix . $directory . "/";
592         }
593     } else {
594         $link_prefix = "";
595     }
596
597     $request_args = $request->args;
598     $timeout = (!$request->getArg('start_debug')) ? 60 : 240;
599     if ($directory) {
600         if (isWindows())
601             $directory = str_replace("\\", "/", $directory); // no Win95 support.
602         if (!is_dir("$directory/images"))
603             mkdir("$directory/images");
604     }
605     $already = array();
606     $outfiles = array();
607     $already_images = array();
608
609     while ($page = $page_iter->next()) {
610         if (is_string($page)) {
611             $pagename = $page;
612             $page = $request->_dbi->getPage($pagename);
613         } else {
614             $pagename = $page->getName();
615         }
616         if (empty($firstpage)) $firstpage = $pagename;
617         if (array_key_exists($pagename, $already))
618             continue;
619         $already[$pagename] = 1;
620         $current = $page->getCurrentRevision();
621         //if ($current->getVersion() == 0)
622         //    continue;
623
624         $request->args = $request_args; // some plugins might change them (esp. on POST)
625         longer_timeout($timeout); // Reset watchdog
626
627         if ($zip) {
628             $attrib = array('mtime' => $current->get('mtime'),
629                 'is_ascii' => 1);
630             if ($page->get('locked'))
631                 $attrib['write_protected'] = 1;
632         } elseif (!$silent) {
633             if (!isa($request, 'MockRequest')) {
634                 PrintXML(HTML::br(), $pagename, ' ... ');
635                 flush();
636             }
637         }
638         if (in_array($pagename, $excludeList)) {
639             if (!$silent and !isa($request, 'MockRequest')) {
640                 PrintXML(_("Skipped."));
641                 flush();
642             }
643             continue;
644         }
645         $relative_base = '';
646         if ($WikiTheme->DUMP_MODE == 'PDFHTML')
647             $request->setArg('action', 'pdf'); // to omit cache headers
648         $request->setArg('pagename', $pagename); // Template::_basepage fix
649         $filename = FilenameForPage($pagename) . $WikiTheme->HTML_DUMP_SUFFIX;
650         $args = array('revision' => $current,
651             'CONTENT' => $current->getTransformedContent(),
652             'relative_base' => $relative_base);
653         // For every %2F will need to mkdir -p dirname($pagename)
654         if (preg_match("/(%2F|\/)/", $filename)) {
655             // mkdir -p and set relative base for subdir pages
656             $filename = preg_replace("/%2F/", "/", $filename);
657             $count = substr_count($filename, "/");
658             $dirname = dirname($filename);
659             if ($directory)
660                 mkdir_p($directory . "/" . $dirname);
661             // Fails with "XX / YY", "XX" is created, "XX / YY" cannot be written
662             // if (isWindows()) // interesting Windows bug: cannot mkdir "bla "
663             // Since dumps needs to be copied, we have to disallow this for all platforms.
664             $filename = preg_replace("/ \//", "/", $filename);
665             $relative_base = "../";
666             while ($count > 1) {
667                 $relative_base .= "../";
668                 $count--;
669             }
670             $args['relative_base'] = $relative_base;
671         }
672         $msg = HTML();
673
674         $DUMP_MODE = $WikiTheme->DUMP_MODE;
675         $data = GeneratePageasXML(new Template('browse', $request, $args),
676             $pagename, $current, $args);
677         $WikiTheme->DUMP_MODE = $DUMP_MODE;
678
679         if (preg_match_all("/<img .*?src=\"(\/.+?)\"/", $data, $m)) {
680             // fix to local relative path for uploaded images, so that pdf will work
681             foreach ($m[1] as $img_file) {
682                 $base = basename($img_file);
683                 $data = str_replace('src="' . $img_file . '"', 'src="images/' . $base . '"', $data);
684                 if (array_key_exists($img_file, $already_images))
685                     continue;
686                 $already_images[$img_file] = 1;
687                 // resolve src from webdata to file
688                 $src = $doc_root . $img_file;
689                 if (file_exists($src) and $base) {
690                     if ($directory) {
691                         $target = "$directory/images/$base";
692                         if (copy($src, $target)) {
693                             if (!$silent)
694                                 _copyMsg($img_file, fmt("... copied to %s", $target));
695                         } else {
696                             if (!$silent)
697                                 _copyMsg($img_file, fmt("... not copied to %s", $target));
698                         }
699                     } else {
700                         $target = "images/$base";
701                         $zip->addSrcFile($target, $src);
702                     }
703                 }
704             }
705         }
706
707         if ($directory) {
708             $outfile = $directory . "/" . $filename;
709             if (!($fd = fopen($outfile, "wb"))) {
710                 $msg->pushContent(HTML::strong(fmt("couldn't open file '%s' for writing",
711                     $outfile)));
712                 $request->finish($msg);
713             }
714             $len = strlen($data);
715             $num = fwrite($fd, $data, $len);
716             if ($pagename != $filename) {
717                 $link = LinkURL($link_prefix . $filename, $filename);
718                 $msg->pushContent(HTML::small(_("saved as "), $link, " ... "));
719             }
720             $msg->pushContent(HTML::small(fmt("%s bytes written", $num), "\n"));
721             if (!$silent) {
722                 if (!isa($request, 'MockRequest')) {
723                     PrintXML($msg);
724                 }
725                 flush();
726                 $request->chunkOutput();
727             }
728             assert($num == $len);
729             fclose($fd);
730             $outfiles[] = $outfile;
731         } else {
732             $zip->addRegularFile($filename, $data, $attrib);
733         }
734
735         if (USECACHE) {
736             $request->_dbi->_cache->invalidate_cache($pagename);
737             unset ($request->_dbi->_cache->_pagedata_cache);
738             unset ($request->_dbi->_cache->_versiondata_cache);
739             unset ($request->_dbi->_cache->_glv_cache);
740         }
741         unset ($request->_dbi->_cache->_backend->_page_data);
742
743         unset($msg);
744         unset($current->_transformedContent);
745         unset($current);
746         if (!empty($template)) {
747             unset($template->_request);
748             unset($template);
749         }
750         unset($data);
751     }
752     $page_iter->free();
753
754     $attrib = false; //array('is_ascii' => 0);
755     if (!empty($WikiTheme->dumped_images) and is_array($WikiTheme->dumped_images)) {
756         // @mkdir("$directory/images");
757         foreach ($WikiTheme->dumped_images as $img_file) {
758             if (array_key_exists($img_file, $already_images))
759                 continue;
760             $already_images[$img_file] = 1;
761             if ($img_file
762                 and ($from = $WikiTheme->_findFile($img_file, true))
763                     and basename($from)
764             ) {
765                 if ($directory) {
766                     $target = "$directory/images/" . basename($from);
767                     if ($silent)
768                         copy($WikiTheme->_path . $from, $target);
769                     else {
770                         if (copy($WikiTheme->_path . $from, $target)) {
771                             _copyMsg($from, fmt("... copied to %s", $target));
772                         } else {
773                             _copyMsg($from, fmt("... not copied to %s", $target));
774                         }
775                     }
776                 } else {
777                     $target = "images/" . basename($from);
778                     $zip->addSrcFile($target, $WikiTheme->_path . $from);
779                 }
780             } elseif (!$silent) {
781                 _copyMsg($from, _("... not found"));
782             }
783         }
784     }
785
786     if (!empty($WikiTheme->dumped_buttons)
787         and is_array($WikiTheme->dumped_buttons)
788     ) {
789         // Buttons also
790         if ($directory && !is_dir("$directory/images/buttons"))
791             mkdir("$directory/images/buttons");
792         foreach ($WikiTheme->dumped_buttons as $text => $img_file) {
793             if (array_key_exists($img_file, $already_images))
794                 continue;
795             $already_images[$img_file] = 1;
796             if ($img_file
797                 and ($from = $WikiTheme->_findFile($img_file, true))
798                     and basename($from)
799             ) {
800                 if ($directory) {
801                     $target = "$directory/images/buttons/" . basename($from);
802                     if ($silent)
803                         copy($WikiTheme->_path . $from, $target);
804                     else {
805                         if (copy($WikiTheme->_path . $from, $target)) {
806                             _copyMsg($from, fmt("... copied to %s", $target));
807                         } else {
808                             _copyMsg($from, fmt("... not copied to %s", $target));
809                         }
810                     }
811                 } else {
812                     $target = "images/buttons/" . basename($from);
813                     $zip->addSrcFile($target, $WikiTheme->_path . $from);
814                 }
815             } elseif (!$silent) {
816                 _copyMsg($from, _("... not found"));
817             }
818         }
819     }
820     if (!empty($WikiTheme->dumped_css) and is_array($WikiTheme->dumped_css)) {
821         foreach ($WikiTheme->dumped_css as $css_file) {
822             if (array_key_exists($css_file, $already_images))
823                 continue;
824             $already_images[$css_file] = 1;
825             if ($css_file
826                 and ($from = $WikiTheme->_findFile(basename($css_file), true))
827                     and basename($from)
828             ) {
829                 // TODO: fix @import url(main.css);
830                 if ($directory) {
831                     $target = "$directory/" . basename($css_file);
832                     if ($silent)
833                         copy($WikiTheme->_path . $from, $target);
834                     else {
835                         if (copy($WikiTheme->_path . $from, $target)) {
836                             _copyMsg($from, fmt("... copied to %s", $target));
837                         } else {
838                             _copyMsg($from, fmt("... not copied to %s", $target));
839                         }
840                     }
841                 } else {
842                     //$attrib = array('is_ascii' => 0);
843                     $target = basename($css_file);
844                     $zip->addSrcFile($target, $WikiTheme->_path . $from);
845                 }
846             } elseif (!$silent) {
847                 _copyMsg($from, _("... not found"));
848             }
849         }
850     }
851
852     if ($zip)
853         $zip->finish();
854
855     if ($WikiTheme->DUMP_MODE == 'PDFHTML') {
856         if (USE_EXTERNAL_HTML2PDF and $outfiles) {
857             $cmd = EXTERNAL_HTML2PDF_PAGELIST . ' "' . join('" "', $outfiles) . '"';
858             $filename = FilenameForPage($firstpage);
859             if (DEBUG) {
860                 $tmpfile = $directory . "/createpdf.bat";
861                 $fp = fopen($tmpfile, "wb");
862                 fwrite($fp, $cmd . " > $filename.pdf");
863                 fclose($fp);
864             }
865             if (!headers_sent()) {
866                 Header('Content-Type: application/pdf');
867                 passthru($cmd);
868             } else {
869                 $tmpdir = getUploadFilePath();
870                 $s = passthru($cmd . " > $tmpdir/$filename.pdf");
871                 $errormsg = "<br />\nGenerated <a href=\"" . getUploadDataPath() . "$filename.pdf\">Upload:$filename.pdf</a>\n";
872                 $errormsg .= $s;
873                 echo $errormsg;
874             }
875             if (!DEBUG) {
876                 foreach ($outfiles as $f) unlink($f);
877             }
878         }
879         if (!empty($errormsg)) {
880             $request->discardOutput();
881             $GLOBALS['ErrorManager']->_postponed_errors = array();
882         }
883     }
884
885     $ErrorManager->popErrorHandler();
886
887     $WikiTheme->HTML_DUMP_SUFFIX = '';
888     $WikiTheme->DUMP_MODE = false;
889     $WikiTheme->_MoreAttr['body'] = isset($_bodyAttr) ? $_bodyAttr : '';
890 }
891
892 ////////////////////////////////////////////////////////////////
893 //
894 //  Functions for restoring.
895 //
896 ////////////////////////////////////////////////////////////////
897
898 function SavePage(&$request, &$pageinfo, $source, $filename)
899 {
900     static $overwite_all = false;
901     $pagedata = $pageinfo['pagedata']; // Page level meta-data.
902     $versiondata = $pageinfo['versiondata']; // Revision level meta-data.
903
904     if (empty($pageinfo['pagename'])) {
905         PrintXML(HTML::p(HTML::strong(_("Empty pagename!"))));
906         return;
907     }
908
909     if (empty($versiondata['author_id']))
910         $versiondata['author_id'] = $versiondata['author'];
911
912     // remove invalid backend specific chars. utf8 issues mostly
913     $pagename_check = new WikiPagename($pageinfo['pagename']);
914     if (!$pagename_check->isValid()) {
915         PrintXML(HTML::p(HTML::strong(sprintf(_("'%s': Bad page name"), $pageinfo['pagename']))));
916         return;
917     }
918     $pagename = $pagename_check->getName();
919     $content = $pageinfo['content'];
920
921     if ($pagename == _("InterWikiMap"))
922         $content = _tryinsertInterWikiMap($content);
923
924     $dbi =& $request->_dbi;
925     $page = $dbi->getPage($pagename);
926
927     // Try to merge if updated pgsrc contents are different. This
928     // whole thing is hackish
929     //
930     // TODO: try merge unless:
931     // if (current contents = default contents && pgsrc_version >=
932     // pgsrc_version) then just upgrade this pgsrc
933     $needs_merge = false;
934     $merging = false;
935     $overwrite = false;
936
937     if ($request->getArg('merge')) {
938         $merging = true;
939     } else if ($request->getArg('overwrite')) {
940         $overwrite = true;
941     }
942
943     $current = $page->getCurrentRevision();
944     $skip = false;
945     $edit = $request->getArg('edit');
946     if ($merging) {
947         if (isset($edit['keep_old'])) {
948             $merging = false;
949             $skip = true;
950         } elseif (isset($edit['overwrite'])) {
951             $merging = false;
952             $overwrite = true;
953         } elseif ($current and (!$current->hasDefaultContents())
954             && ($current->getPackedContent() != $content)
955         ) {
956             include_once 'lib/editpage.php';
957             $request->setArg('pagename', $pagename);
958             $v = $current->getVersion();
959             $request->setArg('revision', $current->getVersion());
960             $p = new LoadFileConflictPageEditor($request);
961             $p->_content = $content;
962             $p->_currentVersion = $v - 1;
963             $p->editPage($saveFailed = true);
964             return; //early return
965         }
966     }
967     if (!$skip)
968         foreach ($pagedata as $key => $value) {
969             if (!empty($value))
970                 $page->set($key, $value);
971         }
972
973     $mesg = HTML::p(array('style' => 'text-indent: 3em;'));
974     if ($source)
975         $mesg->pushContent(' ', fmt("from %s", $source));
976
977     if (!$current) {
978         //FIXME: This should not happen! (empty vdata, corrupt cache or db)
979         $current = $page->getCurrentRevision();
980     }
981     if ($current->getVersion() == 0) {
982         $versiondata['author'] = ADMIN_USER;
983         $versiondata['author_id'] = ADMIN_USER;
984         $mesg->pushContent(' - ', _("New page"));
985         $isnew = true;
986     } else {
987         if ((!$current->hasDefaultContents())
988             && ($current->getPackedContent() != $content)
989         ) {
990             if ($overwrite) {
991                 $mesg->pushContent(' ',
992                     fmt("has edit conflicts - overwriting anyway"));
993                 $skip = false;
994                 if (substr_count($source, 'pgsrc')) {
995                     $versiondata['author'] = ADMIN_USER;
996                     // but leave authorid as userid who loaded the file
997                 }
998             } else {
999                 if (isset($edit['keep_old'])) {
1000                     $mesg->pushContent(' ', fmt("keep old"));
1001                 } else {
1002                     $mesg->pushContent(' ', fmt("has edit conflicts - skipped"));
1003                     $needs_merge = true; // hackish, to display the buttons
1004                 }
1005                 $skip = true;
1006             }
1007         } else if ($current->getPackedContent() == $content) {
1008             // The page content is the same, we don't need a new revision.
1009             $mesg->pushContent(' ',
1010                 fmt("content is identical to current version %d - no new revision created",
1011                     $current->getVersion()));
1012             $skip = true;
1013         }
1014         $isnew = false;
1015     }
1016
1017     if (!$skip) {
1018         // in case of failures print the culprit:
1019         if (!isa($request, 'MockRequest')) {
1020             PrintXML(HTML::p(WikiLink($pagename)));
1021             flush();
1022         }
1023         $new = $page->save($content, WIKIDB_FORCE_CREATE, $versiondata);
1024         $dbi->touch();
1025         $mesg->pushContent(' ', fmt("- saved to database as version %d",
1026             $new->getVersion()));
1027     }
1028     if ($needs_merge) {
1029         $f = $source;
1030         // hackish, $source contains needed path+filename
1031         $f = str_replace(sprintf(_("MIME file %s"), ''), '', $f);
1032         $f = str_replace(sprintf(_("Serialized file %s"), ''), '', $f);
1033         $f = str_replace(sprintf(_("plain file %s"), ''), '', $f);
1034         //check if uploaded file? they pass just the content, but the file is gone
1035         if (@stat($f)) {
1036             global $WikiTheme;
1037             $meb = Button(array('action' => 'loadfile',
1038                     'merge' => true,
1039                     'source' => $f),
1040                 _("Merge Edit"),
1041                 _("PhpWikiAdministration"),
1042                 'wikiadmin');
1043             $owb = Button(array('action' => 'loadfile',
1044                     'overwrite' => true,
1045                     'source' => $f),
1046                 _("Restore Anyway"),
1047                 _("PhpWikiAdministration"),
1048                 'wikiunsafe');
1049             $mesg->pushContent(' ', $meb, " ", $owb);
1050             if (!$overwite_all) {
1051                 $args = $request->getArgs();
1052                 $args['overwrite'] = 1;
1053                 $owb = Button($args,
1054                     _("Overwrite All"),
1055                     _("PhpWikiAdministration"),
1056                     'wikiunsafe');
1057                 $mesg->pushContent(HTML::span(array('class' => 'hint'), $owb));
1058                 $overwite_all = true;
1059             }
1060         } else {
1061             $mesg->pushContent(HTML::em(_(" Sorry, cannot merge.")));
1062         }
1063     }
1064
1065     if (!isa($request, 'MockRequest')) {
1066         if ($skip)
1067             PrintXML(HTML::p(HTML::em(WikiLink($pagename))), $mesg);
1068         else
1069             PrintXML($mesg);
1070         flush();
1071     }
1072 }
1073
1074 // action=revert (by diff)
1075 function RevertPage(&$request)
1076 {
1077     $mesg = HTML::div();
1078     $pagename = $request->getArg('pagename');
1079     $version = $request->getArg('version');
1080     $dbi =& $request->_dbi;
1081     $page = $dbi->getPage($pagename);
1082     if (!$version) {
1083         $request->redirect(WikiURL($page,
1084             array('warningmsg' => _('Revert: missing required version argument'))));
1085         // noreturn
1086     }
1087     $current = $page->getCurrentRevision();
1088     $currversion = $current->getVersion();
1089     if ($currversion == 0) {
1090         $request->redirect(WikiURL($page,
1091             array('errormsg' => _('No revert: no page content'))));
1092         // noreturn
1093     }
1094     if ($currversion == $version) {
1095         $request->redirect(WikiURL($page,
1096             array('warningmsg' => _('No revert: same version page'))));
1097         // noreturn
1098     }
1099     if ($request->getArg('cancel')) {
1100         $request->redirect(WikiURL($page,
1101             array('warningmsg' => _('Revert cancelled'))));
1102         // noreturn
1103     }
1104     if (!$request->getArg('verify')) {
1105         $mesg->pushContent(HTML::p(fmt("Are you sure to revert %s to version $version?", WikiLink($pagename))),
1106             HTML::form(array('action' => $request->getPostURL(),
1107                     'method' => 'post'),
1108                 HiddenInputs($request->getArgs(), false, array('verify')),
1109                 HiddenInputs(array('verify' => 1)),
1110                 Button('submit:verify', _("Yes"), 'button'),
1111                 HTML::Raw('&nbsp;'),
1112                 Button('submit:cancel', _("Cancel"), 'button'))
1113         );
1114         $rev = $page->getRevision($version);
1115         $html = HTML(HTML::fieldset($mesg), HTML::hr(), $rev->getTransformedContent());
1116         $template = Template('browse',
1117             array('CONTENT' => $html));
1118         GeneratePage($template, $pagename, $rev);
1119         $request->checkValidators();
1120         flush();
1121         return;
1122     }
1123     $rev = $page->getRevision($version);
1124     $content = $rev->getPackedContent();
1125     $versiondata = $rev->_data;
1126     $versiondata['summary'] = sprintf(_("revert to version %d"), $version);
1127     $versiondata['mtime'] = time();
1128     $new = $page->save($content, $currversion + 1, $versiondata);
1129     $dbi->touch();
1130
1131     $mesg = HTML::span();
1132     $pagelink = WikiLink($pagename);
1133     $mesg->pushContent(fmt("Revert: %s", $pagelink),
1134         fmt("- version %d saved to database as version %d",
1135             $version, $new->getVersion()));
1136     // Force browse of current page version.
1137     $request->setArg('version', false);
1138     $template = Template('savepage', array());
1139     $template->replace('CONTENT', $new->getTransformedContent());
1140
1141     GeneratePage($template, $mesg, $new);
1142     flush();
1143 }
1144
1145 function _tryinsertInterWikiMap($content)
1146 {
1147     $goback = false;
1148     if (strpos($content, "<verbatim>")) {
1149         //$error_html = " The newly loaded pgsrc already contains a verbatim block.";
1150         $goback = true;
1151     }
1152     if (!$goback && !defined('INTERWIKI_MAP_FILE')) {
1153         $error_html = sprintf(" " . _("%s: not defined"), "INTERWIKI_MAP_FILE");
1154         $goback = true;
1155     }
1156     $mapfile = FindFile(INTERWIKI_MAP_FILE, 1);
1157     if (!$goback && !file_exists($mapfile)) {
1158         $error_html = sprintf(" " . _("%s: file not found"), INTERWIKI_MAP_FILE);
1159         $goback = true;
1160     }
1161
1162     if (!empty($error_html))
1163         trigger_error(_("Default InterWiki map file not loaded.")
1164             . $error_html, E_USER_NOTICE);
1165     if ($goback)
1166         return $content;
1167
1168     // if loading from virgin setup do echo, otherwise trigger_error E_USER_NOTICE
1169     if (!isa($GLOBALS['request'], 'MockRequest'))
1170         echo sprintf(_("Loading InterWikiMap from external file %s."), $mapfile), "<br />";
1171
1172     $fd = fopen($mapfile, "rb");
1173     $data = fread($fd, filesize($mapfile));
1174     fclose($fd);
1175     $content = $content . "\n<verbatim>\n$data</verbatim>\n";
1176     return $content;
1177 }
1178
1179 function ParseSerializedPage($text, $default_pagename, $user)
1180 {
1181     if (!preg_match('/^a:\d+:{[si]:\d+/', $text))
1182         return false;
1183
1184     $pagehash = unserialize($text);
1185
1186     // Split up pagehash into four parts:
1187     //   pagename
1188     //   content
1189     //   page-level meta-data
1190     //   revision-level meta-data
1191
1192     if (!defined('FLAG_PAGE_LOCKED'))
1193         define('FLAG_PAGE_LOCKED', 1);
1194     if (!defined('FLAG_PAGE_EXTERNAL'))
1195         define('FLAG_PAGE_EXTERNAL', 1);
1196     $pageinfo = array('pagedata' => array(),
1197         'versiondata' => array());
1198
1199     $pagedata = &$pageinfo['pagedata'];
1200     $versiondata = &$pageinfo['versiondata'];
1201
1202     // Fill in defaults.
1203     if (empty($pagehash['pagename']))
1204         $pagehash['pagename'] = $default_pagename;
1205     if (empty($pagehash['author'])) {
1206         $pagehash['author'] = $user->getId();
1207     }
1208
1209     foreach ($pagehash as $key => $value) {
1210         switch ($key) {
1211             case 'pagename':
1212             case 'version':
1213             case 'hits':
1214                 $pageinfo[$key] = $value;
1215                 break;
1216             case 'content':
1217                 $pageinfo[$key] = join("\n", $value);
1218                 break;
1219             case 'flags':
1220                 if (($value & FLAG_PAGE_LOCKED) != 0)
1221                     $pagedata['locked'] = 'yes';
1222                 if (($value & FLAG_PAGE_EXTERNAL) != 0)
1223                     $pagedata['external'] = 'yes';
1224                 break;
1225             case 'owner':
1226             case 'created':
1227                 $pagedata[$key] = $value;
1228                 break;
1229             case 'acl':
1230             case 'perm':
1231                 $pagedata['perm'] = ParseMimeifiedPerm($value);
1232                 break;
1233             case 'lastmodified':
1234                 $versiondata['mtime'] = $value;
1235                 break;
1236             case 'author':
1237             case 'author_id':
1238             case 'summary':
1239                 $versiondata[$key] = $value;
1240                 break;
1241         }
1242     }
1243     if (empty($pagehash['charset']))
1244         $pagehash['charset'] = 'utf-8';
1245     // compare to target charset
1246     if (strtolower($pagehash['charset']) != strtolower($GLOBALS['charset'])) {
1247         $pageinfo['content'] = charset_convert($params['charset'], $GLOBALS['charset'], $pageinfo['content']);
1248         $pageinfo['pagename'] = charset_convert($params['charset'], $GLOBALS['charset'], $pageinfo['pagename']);
1249     }
1250     return $pageinfo;
1251 }
1252
1253 function SortByPageVersion($a, $b)
1254 {
1255     return $a['version'] - $b['version'];
1256 }
1257
1258 /**
1259  * Security alert! We should not allow to import config.ini into our wiki (or from a sister wiki?)
1260  * because the sql passwords are in plaintext there. And the webserver must be able to read it.
1261  * Detected by Santtu Jarvi.
1262  */
1263 function LoadFile(&$request, $filename, $text = false, $mtime = false)
1264 {
1265     if (preg_match("/config$/", dirname($filename)) // our or other config
1266         and preg_match("/config.*\.ini/", basename($filename))
1267     ) // backups and other versions also
1268     {
1269         trigger_error(sprintf("Refused to load %s", $filename), E_USER_WARNING);
1270         return;
1271     }
1272     if (!is_string($text)) {
1273         // Read the file.
1274         $stat = stat($filename);
1275         $mtime = $stat[9];
1276         $text = implode("", file($filename));
1277     }
1278
1279     if (!$request->getArg('start_debug')) @set_time_limit(30); // Reset watchdog
1280     else @set_time_limit(240);
1281
1282     // FIXME: basename("filewithnoslashes") seems to return garbage sometimes.
1283     $basename = basename("/dummy/" . $filename);
1284
1285     if (!$mtime)
1286         $mtime = time(); // Last resort.
1287
1288     // DONE: check source - target charset for content and pagename
1289     // but only for pgsrc'ed content, not from the browser.
1290
1291     $default_pagename = rawurldecode($basename);
1292     if (($parts = ParseMimeifiedPages($text))) {
1293         if (count($parts) > 1)
1294             $overwrite = $request->getArg('overwrite');
1295         usort($parts, 'SortByPageVersion');
1296         foreach ($parts as $pageinfo) {
1297             // force overwrite
1298             if (count($parts) > 1)
1299                 $request->setArg('overwrite', 1);
1300             SavePage($request, $pageinfo, sprintf(_("MIME file %s"),
1301                 $filename), $basename);
1302         }
1303         if (count($parts) > 1)
1304             if ($overwrite)
1305                 $request->setArg('overwrite', $overwrite);
1306             else
1307                 unset($request->_args['overwrite']);
1308     } else if (($pageinfo = ParseSerializedPage($text, $default_pagename,
1309         $request->getUser()))
1310     ) {
1311         SavePage($request, $pageinfo, sprintf(_("Serialized file %s"),
1312             $filename), $basename);
1313     } else {
1314         // plain old file
1315         $user = $request->getUser();
1316
1317         $file_charset = 'utf-8';
1318         // compare to target charset
1319         if ($file_charset != strtolower($GLOBALS['charset'])) {
1320             $text = charset_convert($file_charset, $GLOBALS['charset'], $text);
1321             $default_pagename = charset_convert($file_charset, $GLOBALS['charset'], $default_pagename);
1322         }
1323
1324         // Assume plain text file.
1325         $pageinfo = array('pagename' => $default_pagename,
1326             'pagedata' => array(),
1327             'versiondata'
1328             => array('author' => $user->getId()),
1329             'content' => preg_replace('/[ \t\r]*\n/', "\n",
1330                 chop($text))
1331         );
1332         SavePage($request, $pageinfo, sprintf(_("plain file %s"), $filename),
1333             $basename);
1334     }
1335 }
1336
1337 function LoadZip(&$request, $zipfile, $files = false, $exclude = false)
1338 {
1339     $zip = new ZipReader($zipfile);
1340     $timeout = (!$request->getArg('start_debug')) ? 20 : 120;
1341     while (list ($fn, $data, $attrib) = $zip->readFile()) {
1342         // FIXME: basename("filewithnoslashes") seems to return
1343         // garbage sometimes.
1344         $fn = basename("/dummy/" . $fn);
1345         if (($files && !in_array($fn, $files))
1346             || ($exclude && in_array($fn, $exclude))
1347         ) {
1348             PrintXML(HTML::p(WikiLink($fn)),
1349                 HTML::p(_("Skipping")));
1350             flush();
1351             continue;
1352         }
1353         longer_timeout($timeout); // longer timeout per page
1354         LoadFile($request, $fn, $data, $attrib['mtime']);
1355     }
1356 }
1357
1358 function LoadDir(&$request, $dirname, $files = false, $exclude = false)
1359 {
1360     $fileset = new LimitedFileSet($dirname, $files, $exclude);
1361
1362     if (!$files and ($skiplist = $fileset->getSkippedFiles())) {
1363         PrintXML(HTML::p(HTML::strong(_("Skipping"))));
1364         $list = HTML::ul();
1365         foreach ($skiplist as $file)
1366             $list->pushContent(HTML::li(WikiLink($file)));
1367         PrintXML(HTML::p($list));
1368     }
1369
1370     // Defer HomePage loading until the end. If anything goes wrong
1371     // the pages can still be loaded again.
1372     $files = $fileset->getFiles();
1373     if (in_array(HOME_PAGE, $files)) {
1374         $files = array_diff($files, array(HOME_PAGE));
1375         $files[] = HOME_PAGE;
1376     }
1377     $timeout = (!$request->getArg('start_debug')) ? 20 : 120;
1378     foreach ($files as $file) {
1379         longer_timeout($timeout); // longer timeout per page
1380         if (substr($file, -1, 1) != '~') // refuse to load backup files
1381             LoadFile($request, "$dirname/$file");
1382     }
1383 }
1384
1385 class LimitedFileSet extends FileSet
1386 {
1387     function LimitedFileSet($dirname, $_include, $exclude)
1388     {
1389         $this->_includefiles = $_include;
1390         $this->_exclude = $exclude;
1391         $this->_skiplist = array();
1392         parent::FileSet($dirname);
1393     }
1394
1395     function _filenameSelector($fn)
1396     {
1397         $incl = &$this->_includefiles;
1398         $excl = &$this->_exclude;
1399
1400         if (($incl && !in_array($fn, $incl))
1401             || ($excl && in_array($fn, $excl))
1402         ) {
1403             $this->_skiplist[] = $fn;
1404             return false;
1405         } else {
1406             return true;
1407         }
1408     }
1409
1410     function getSkippedFiles()
1411     {
1412         return $this->_skiplist;
1413     }
1414 }
1415
1416 function IsZipFile($filename_or_fd)
1417 {
1418     // See if it looks like zip file
1419     if (is_string($filename_or_fd)) {
1420         $fd = fopen($filename_or_fd, "rb");
1421         $magic = fread($fd, 4);
1422         fclose($fd);
1423     } else {
1424         $fpos = ftell($filename_or_fd);
1425         $magic = fread($filename_or_fd, 4);
1426         fseek($filename_or_fd, $fpos);
1427     }
1428
1429     return $magic == ZIP_LOCHEAD_MAGIC || $magic == ZIP_CENTHEAD_MAGIC;
1430 }
1431
1432 function LoadAny(&$request, $file_or_dir, $files = false, $exclude = false)
1433 {
1434     // Try urlencoded filename for accented characters.
1435     if (!file_exists($file_or_dir)) {
1436         // Make sure there are slashes first to avoid confusing phps
1437         // with broken dirname or basename functions.
1438         // FIXME: windows uses \ and :
1439         if (is_integer(strpos($file_or_dir, "/"))) {
1440             $newfile = FindFile($file_or_dir, true);
1441             // Panic. urlencoded by the browser (e.g. San%20Diego => San Diego)
1442             if (!$newfile)
1443                 $file_or_dir = dirname($file_or_dir) . "/"
1444                     . rawurlencode(basename($file_or_dir));
1445         } else {
1446             // This is probably just a file.
1447             $file_or_dir = rawurlencode($file_or_dir);
1448         }
1449     }
1450
1451     $type = filetype($file_or_dir);
1452     if ($type == 'link') {
1453         // For symbolic links, use stat() to determine
1454         // the type of the underlying file.
1455         list(, , $mode) = stat($file_or_dir);
1456         $type = ($mode >> 12) & 017;
1457         if ($type == 010)
1458             $type = 'file';
1459         elseif ($type == 004)
1460             $type = 'dir';
1461     }
1462
1463     if (!$type) {
1464         $request->finish(fmt("Empty or not existing source. Unable to load: %s", $file_or_dir));
1465     } else if ($type == 'dir') {
1466         LoadDir($request, $file_or_dir, $files, $exclude);
1467     } else if ($type != 'file' && !preg_match('/^(http|ftp):/', $file_or_dir)) {
1468         $request->finish(fmt("Bad file type: %s", $type));
1469     } else if (IsZipFile($file_or_dir)) {
1470         LoadZip($request, $file_or_dir, $files, $exclude);
1471     } else /* if (!$files || in_array(basename($file_or_dir), $files)) */ {
1472         LoadFile($request, $file_or_dir);
1473     }
1474 }
1475
1476 function LoadFileOrDir(&$request)
1477 {
1478     $source = $request->getArg('source');
1479     $finder = new FileFinder;
1480     $source = $finder->slashifyPath($source);
1481     StartLoadDump($request,
1482         sprintf(_("Loading '%s'"), $source));
1483     LoadAny($request, $source);
1484     EndLoadDump($request);
1485 }
1486
1487 /**
1488  * HomePage was not found so first-time install is supposed to run.
1489  * - import all pgsrc pages.
1490  * - Todo: installer interface to edit config/config.ini settings
1491  * - Todo: ask for existing old index.php to convert to config/config.ini
1492  * - Todo: theme-specific pages:
1493  *   blog - HomePage, ADMIN_USER/Blogs
1494  */
1495 function SetupWiki(&$request)
1496 {
1497     global $GenericPages, $LANG;
1498
1499     //FIXME: This is a hack (err, "interim solution")
1500     // This is a bogo-bogo-login:  Login without
1501     // saving login information in session state.
1502     // This avoids logging in the unsuspecting
1503     // visitor as ADMIN_USER
1504     //
1505     // This really needs to be cleaned up...
1506     // (I'm working on it.)
1507     $real_user = $request->_user;
1508     if (ENABLE_USER_NEW)
1509         $request->_user = new _BogoUser(ADMIN_USER);
1510
1511     else
1512         $request->_user = new WikiUser($request, ADMIN_USER, WIKIAUTH_BOGO);
1513
1514     StartLoadDump($request, _("Loading up virgin wiki"));
1515
1516     $pgsrc = FindLocalizedFile(WIKI_PGSRC);
1517     $default_pgsrc = FindFile(DEFAULT_WIKI_PGSRC);
1518
1519     $request->setArg('overwrite', true);
1520     if ($default_pgsrc != $pgsrc) {
1521         LoadAny($request, $default_pgsrc, $GenericPages);
1522     }
1523     $request->setArg('overwrite', false);
1524     LoadAny($request, $pgsrc);
1525     $dbi =& $request->_dbi;
1526
1527     // Ensure that all mandatory pages are loaded
1528     $finder = new FileFinder;
1529
1530     if (!FUSIONFORGE) {
1531         $mandatory = explode(':', 'SandBox:Template/Category:Template/Talk:SpecialPages:CategoryCategory:CategoryActionPage:Help/OldTextFormattingRules:Help/TextFormattingRules:PhpWikiAdministration');
1532     } elseif (WIKI_NAME == "help") {
1533         $mandatory = explode(':', 'SandBox:Template/Category:Template/Talk:SpecialPages:CategoryCategory:CategoryActionPage:Help/TextFormattingRules:PhpWikiAdministration');
1534     } else {
1535         $mandatory = explode(':', 'SandBox:Template/UserPage:Template/Category:Template/Talk:SpecialPages:CategoryCategory:CategoryActionPage:TextFormattingRules:PhpWikiAdministration');
1536     }
1537     foreach (array_merge($mandatory,
1538         $GLOBALS['AllActionPages'],
1539         array(constant('HOME_PAGE'))) as $f) {
1540         $page = gettext($f);
1541         $epage = urlencode($page);
1542         if (!$dbi->isWikiPage($page)) {
1543             // translated version provided?
1544             if ($lf = FindLocalizedFile($pgsrc . $finder->_pathsep . $epage, 1)) {
1545                 LoadAny($request, $lf);
1546             } else { // load english version of required action page
1547                 LoadAny($request, FindFile(DEFAULT_WIKI_PGSRC . $finder->_pathsep . urlencode($f)));
1548                 $page = $f;
1549             }
1550         }
1551         if (!$dbi->isWikiPage($page)) {
1552             trigger_error(sprintf("Mandatory file %s couldn't be loaded!", $page),
1553                 E_USER_WARNING);
1554         }
1555     }
1556
1557     $pagename = _("InterWikiMap");
1558     $map = $dbi->getPage($pagename);
1559     $map->set('locked', true);
1560     PrintXML(HTML::p(HTML::em(WikiLink($pagename)), HTML::strong(" locked")));
1561     EndLoadDump($request);
1562 }
1563
1564 function LoadPostFile(&$request)
1565 {
1566     $upload = $request->getUploadedFile('file');
1567
1568     if (!$upload)
1569         $request->finish(_("No uploaded file to upload?")); // FIXME: more concise message
1570
1571     // Dump http headers.
1572     StartLoadDump($request, sprintf(_("Uploading %s"), $upload->getName()));
1573
1574     $fd = $upload->open();
1575     if (IsZipFile($fd))
1576         LoadZip($request, $fd, false, array(_("RecentChanges")));
1577     else
1578         LoadFile($request, $upload->getName(), $upload->getContents());
1579
1580     EndLoadDump($request);
1581 }
1582
1583 // Local Variables:
1584 // mode: php
1585 // tab-width: 8
1586 // c-basic-offset: 4
1587 // c-hanging-comment-ender-p: nil
1588 // indent-tabs-mode: nil
1589 // End: