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