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