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