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