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