]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/loadsave.php
Catch fatals on the four dump calls (as file and zip, as html and mimified)
[SourceForge/phpwiki.git] / lib / loadsave.php
1 <?php //-*-php-*-
2 rcs_id('$Id: loadsave.php,v 1.106 2004-06-13 13:54:25 rurban Exp $');
3
4 /*
5  Copyright 1999, 2000, 2001, 2002 $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
25 require_once("lib/ziplib.php");
26 require_once("lib/Template.php");
27
28 /**
29  * ignore fatal errors during dump
30  */
31 function _ignore_fatal_plugin_error_handler(&$error) {
32     if ($error->isFatal()) {
33         $error->errno = E_USER_WARNING;
34         return true;
35     }
36     return false; // let the message come through: call the remaining handlers:
37     /*
38     if (preg_match('/Plugin/',$error->errstr))
39         return true;        // Ignore error
40     */
41 }
42
43 function StartLoadDump(&$request, $title, $html = '')
44 {
45     // FIXME: This is a hack
46     $tmpl = Template('html', array('TITLE' => $title,
47                                   'HEADER' => $title,
48                                   'CONTENT' => '%BODY%'));
49     echo ereg_replace('%BODY%.*', '', $tmpl->getExpansion($html));
50
51     /* ignore fatals in plugins */
52     global $ErrorManager;
53     $ErrorManager->pushErrorHandler(new WikiFunctionCb('_ignore_fatal_plugin_error_handler'));
54 }
55
56 function EndLoadDump(&$request)
57 {
58     global $ErrorManager;
59     $ErrorManager->popErrorHandler();
60     // FIXME: This is a hack
61     $pagelink = WikiLink($request->getPage());
62
63     PrintXML(HTML::p(HTML::strong(_("Complete."))),
64              HTML::p(fmt("Return to %s", $pagelink)));
65     echo "</body></html>\n";
66 }
67
68
69 ////////////////////////////////////////////////////////////////
70 //
71 //  Functions for dumping.
72 //
73 ////////////////////////////////////////////////////////////////
74
75 /**
76  * For reference see:
77  * http://www.nacs.uci.edu/indiv/ehood/MIME/2045/rfc2045.html
78  * http://www.faqs.org/rfcs/rfc2045.html
79  * (RFC 1521 has been superceeded by RFC 2045 & others).
80  *
81  * Also see http://www.faqs.org/rfcs/rfc2822.html
82  */
83 function MailifyPage ($page, $nversions = 1)
84 {
85     $current = $page->getCurrentRevision();
86     $head = '';
87
88     if (STRICT_MAILABLE_PAGEDUMPS) {
89         $from = defined('SERVER_ADMIN') ? SERVER_ADMIN : 'foo@bar';
90         //This is for unix mailbox format: (not RFC (2)822)
91         // $head .= "From $from  " . CTime(time()) . "\r\n";
92         $head .= "Subject: " . rawurlencode($page->getName()) . "\r\n";
93         $head .= "From: $from (PhpWiki)\r\n";
94         // RFC 2822 requires only a Date: and originator (From:)
95         // field, however the obsolete standard RFC 822 also
96         // requires a destination field.
97         $head .= "To: $from (PhpWiki)\r\n";
98     }
99     $head .= "Date: " . Rfc2822DateTime($current->get('mtime')) . "\r\n";
100     $head .= sprintf("Mime-Version: 1.0 (Produced by PhpWiki %s)\r\n",
101                      PHPWIKI_VERSION);
102
103     // This should just be entered by hand (or by script?)
104     // in the actual pgsrc files, since only they should have
105     // RCS ids.
106     //$head .= "X-Rcs-Id: \$Id\$\r\n";
107
108     $iter = $page->getAllRevisions();
109     $parts = array();
110     while ($revision = $iter->next()) {
111         $parts[] = MimeifyPageRevision($revision);
112         if ($nversions > 0 && count($parts) >= $nversions)
113             break;
114     }
115     if (count($parts) > 1)
116         return $head . MimeMultipart($parts);
117     assert($parts);
118     return $head . $parts[0];
119 }
120
121 /***
122  * Compute filename to used for storing contents of a wiki page.
123  *
124  * Basically we do a rawurlencode() which encodes everything except
125  * ASCII alphanumerics and '.', '-', and '_'.
126  *
127  * But we also want to encode leading dots to avoid filenames like
128  * '.', and '..'. (Also, there's no point in generating "hidden" file
129  * names, like '.foo'.)
130  *
131  * @param $pagename string Pagename.
132  * @return string Filename for page.
133  */
134 function FilenameForPage ($pagename)
135 {
136     $enc = rawurlencode($pagename);
137     return preg_replace('/^\./', '%2e', $enc);
138 }
139
140 /**
141  * The main() function which generates a zip archive of a PhpWiki.
142  *
143  * If $include_archive is false, only the current version of each page
144  * is included in the zip file; otherwise all archived versions are
145  * included as well.
146  */
147 function MakeWikiZip (&$request)
148 {
149     if ($request->getArg('include') == 'all') {
150         $zipname         = WIKI_NAME . _("FullDump") . date('Ymd-Hi') . '.zip';
151         $include_archive = true;
152     }
153     else {
154         $zipname         = WIKI_NAME . _("LatestSnapshot") . date('Ymd-Hi') . '.zip';
155         $include_archive = false;
156     }
157
158
159     $zip = new ZipWriter("Created by PhpWiki " . PHPWIKI_VERSION, $zipname);
160
161     /* ignore fatals in plugins */
162     global $ErrorManager;
163     $ErrorManager->pushErrorHandler(new WikiFunctionCb('_ignore_fatal_plugin_error_handler'));
164
165     $dbi = $request->getDbh();
166     $pages = $dbi->getAllPages();
167     while ($page = $pages->next()) {
168         if (! $request->getArg('start_debug'))
169             @set_time_limit(30); // Reset watchdog
170
171         $current = $page->getCurrentRevision();
172         if ($current->getVersion() == 0)
173             continue;
174
175         $wpn = new WikiPageName($page->getName());
176         if (!$wpn->isValid())
177             continue;
178
179         $attrib = array('mtime'    => $current->get('mtime'),
180                         'is_ascii' => 1);
181         if ($page->get('locked'))
182             $attrib['write_protected'] = 1;
183
184         if ($include_archive)
185             $content = MailifyPage($page, 0);
186         else
187             $content = MailifyPage($page);
188
189         $zip->addRegularFile( FilenameForPage($page->getName()),
190                               $content, $attrib);
191     }
192     $zip->finish();
193     $ErrorManager->popErrorHandler();
194 }
195
196 function DumpToDir (&$request)
197 {
198     $directory = $request->getArg('directory');
199     if (empty($directory))
200         $request->finish(_("You must specify a directory to dump to"));
201
202     // see if we can access the directory the user wants us to use
203     if (! file_exists($directory)) {
204         if (! mkdir($directory, 0755))
205             $request->finish(fmt("Cannot create directory '%s'", $directory));
206         else
207             $html = HTML::p(fmt("Created directory '%s' for the page dump...",
208                                 $directory));
209     } else {
210         $html = HTML::p(fmt("Using directory '%s'", $directory));
211     }
212
213     StartLoadDump($request, _("Dumping Pages"), $html);
214
215     $dbi = $request->getDbh();
216     $pages = $dbi->getAllPages();
217
218     while ($page = $pages->next()) {
219         if (! $request->getArg('start_debug'))
220           @set_time_limit(30); // Reset watchdog.
221
222         $filename = FilenameForPage($page->getName());
223
224         $msg = HTML(HTML::br(), $page->getName(), ' ... ');
225
226         if($page->getName() != $filename) {
227             $msg->pushContent(HTML::small(fmt("saved as %s", $filename)),
228                               " ... ");
229         }
230
231         if ($request->getArg('include') == 'all')
232             $data = MailifyPage($page, 0);
233         else
234             $data = MailifyPage($page);
235
236         if ( !($fd = fopen("$directory/$filename", "wb")) ) {
237             $msg->pushContent(HTML::strong(fmt("couldn't open file '%s' for writing",
238                                                "$directory/$filename")));
239             $request->finish($msg);
240         }
241
242         $num = fwrite($fd, $data, strlen($data));
243         $msg->pushContent(HTML::small(fmt("%s bytes written", $num)));
244         PrintXML($msg);
245
246         flush();
247         assert($num == strlen($data));
248         fclose($fd);
249     }
250
251     EndLoadDump($request);
252 }
253
254
255 function DumpHtmlToDir (&$request)
256 {
257     $directory = $request->getArg('directory');
258     if (empty($directory))
259         $request->finish(_("You must specify a directory to dump to"));
260
261     // see if we can access the directory the user wants us to use
262     if (! file_exists($directory)) {
263         if (! mkdir($directory, 0755))
264             $request->finish(fmt("Cannot create directory '%s'", $directory));
265         else
266             $html = HTML::p(fmt("Created directory '%s' for the page dump...",
267                                 $directory));
268     } else {
269         $html = HTML::p(fmt("Using directory '%s'", $directory));
270     }
271
272     StartLoadDump($request, _("Dumping Pages"), $html);
273     $thispage = $request->getArg('pagename'); // for "Return to ..."
274
275     $dbi = $request->getDbh();
276     $pages = $dbi->getAllPages();
277
278     global $Theme;
279     if (defined('HTML_DUMP_SUFFIX'))
280         $Theme->HTML_DUMP_SUFFIX = HTML_DUMP_SUFFIX;
281     $Theme->DUMP_MODE = 'HTML';
282
283     while ($page = $pages->next()) {
284         if (! $request->getArg('start_debug'))
285           @set_time_limit(30); // Reset watchdog.
286
287         $pagename = $page->getName();
288         $request->setArg('pagename',$pagename); // Template::_basepage fix
289         $filename = FilenameForPage($pagename) . $Theme->HTML_DUMP_SUFFIX;
290
291         $msg = HTML(HTML::br(), $pagename, ' ... ');
292
293         $revision = $page->getCurrentRevision();
294         $transformedContent = $revision->getTransformedContent();
295         $template = new Template('browse', $request,
296                                  array('revision' => $revision,
297                                        'CONTENT' => $transformedContent));
298
299         $data = GeneratePageasXML($template, $pagename);
300
301         if ( !($fd = fopen("$directory/$filename", "wb")) ) {
302             $msg->pushContent(HTML::strong(fmt("couldn't open file '%s' for writing",
303                                                "$directory/$filename")));
304             $request->finish($msg);
305         }
306         $num = fwrite($fd, $data, strlen($data));
307         if($page->getName() != $filename) {
308             $prefix = '';
309             if (isWindows()) {  
310                 // drive where apache is installed
311                 $prefix = '/' . substr($_SERVER["DOCUMENT_ROOT"],0,2);
312             }
313             $link = LinkURL("file://".$prefix.$directory."/".$filename, 
314                             $filename);
315             $msg->pushContent(HTML::small(_("saved as "), $link, " ... "));
316         }
317         $msg->pushContent(HTML::small(fmt("%s bytes written", $num), "\n"));
318         PrintXML($msg);
319
320         flush();
321         assert($num == strlen($data));
322         fclose($fd);
323     }
324
325     if (is_array($Theme->dumped_images)) {
326         @mkdir("$directory/images");
327         foreach ($Theme->dumped_images as $img_file) {
328             if (($from = $Theme->_findFile($img_file)) and basename($from)) {
329                 $target = "$directory/images/".basename($img_file);
330                 if (copy($Theme->_path . $from, $target)) {
331                     $msg = HTML(HTML::br(), HTML($from), HTML::small(fmt("... copied to %s", $target)));
332                     PrintXML($msg);
333                 }
334             } else {
335                 $msg = HTML(HTML::br(), HTML($from), HTML::small(fmt("... not found", $target)));
336                 PrintXML($msg);
337             }
338         }
339     }
340     if (is_array($Theme->dumped_css)) {
341       foreach ($Theme->dumped_css as $css_file) {
342           if (($from = $Theme->_findFile(basename($css_file))) and basename($from)) {
343               $target = "$directory/" . basename($css_file);
344               if (copy($Theme->_path . $from, $target)) {
345                   $msg = HTML(HTML::br(), HTML($from), HTML::small(fmt("... copied to %s", $target)));
346                   PrintXML($msg);
347               }
348           } else {
349               $msg = HTML(HTML::br(), HTML($from), HTML::small(fmt("... not found", $target)));
350               PrintXML($msg);
351           }
352       }
353     }
354     $Theme->HTML_DUMP_SUFFIX = '';
355     $Theme->DUMP_MODE = false;
356
357     $request->setArg('pagename',$thispage); // Template::_basepage fix
358     EndLoadDump($request);
359 }
360
361 /* Known problem: any plugins or other code which echo()s text will
362  * lead to a corrupted html zip file which may produce the following
363  * errors upon unzipping:
364  *
365  * warning [wikihtml.zip]:  2401 extra bytes at beginning or within zipfile
366  * file #58:  bad zipfile offset (local header sig):  177561
367  *  (attempting to re-compensate)
368  *
369  * However, the actual wiki page data should be unaffected.
370  */
371 function MakeWikiZipHtml (&$request)
372 {
373     $zipname = "wikihtml.zip";
374     $zip = new ZipWriter("Created by PhpWiki " . PHPWIKI_VERSION, $zipname);
375     $dbi = $request->getDbh();
376     $pages = $dbi->getAllPages();
377
378     global $Theme;
379     if (defined('HTML_DUMP_SUFFIX'))
380         $Theme->HTML_DUMP_SUFFIX = HTML_DUMP_SUFFIX;
381
382     /* ignore fatals in plugins */
383     global $ErrorManager;
384     $ErrorManager->pushErrorHandler(new WikiFunctionCb('_ignore_fatal_plugin_error_handler'));
385
386     while ($page = $pages->next()) {
387         if (! $request->getArg('start_debug'))
388             @set_time_limit(30); // Reset watchdog.
389
390         $current = $page->getCurrentRevision();
391         if ($current->getVersion() == 0)
392             continue;
393
394         $attrib = array('mtime'    => $current->get('mtime'),
395                         'is_ascii' => 1);
396         if ($page->get('locked'))
397             $attrib['write_protected'] = 1;
398
399         $pagename = $page->getName();
400         $request->setArg('pagename',$pagename); // Template::_basepage fix
401         $filename = FilenameForPage($pagename) . $Theme->HTML_DUMP_SUFFIX;
402         $revision = $page->getCurrentRevision();
403
404         $transformedContent = $revision->getTransformedContent();
405
406         $template = new Template('browse', $request,
407                                  array('revision' => $revision,
408                                        'CONTENT' => $transformedContent));
409
410         $data = GeneratePageasXML($template, $pagename);
411
412         $zip->addRegularFile( $filename, $data, $attrib);
413     }
414     // FIXME: Deal with images here.
415     $zip->finish();
416     $ErrorManager->popErrorHandler();
417     $Theme->$HTML_DUMP_SUFFIX = '';
418 }
419
420
421 ////////////////////////////////////////////////////////////////
422 //
423 //  Functions for restoring.
424 //
425 ////////////////////////////////////////////////////////////////
426
427 function SavePage (&$request, $pageinfo, $source, $filename)
428 {
429     $pagedata    = $pageinfo['pagedata'];    // Page level meta-data.
430     $versiondata = $pageinfo['versiondata']; // Revision level meta-data.
431
432     if (empty($pageinfo['pagename'])) {
433         PrintXML(HTML::dt(HTML::strong(_("Empty pagename!"))));
434         return;
435     }
436
437     if (empty($versiondata['author_id']))
438         $versiondata['author_id'] = $versiondata['author'];
439
440     $pagename = $pageinfo['pagename'];
441     $content  = $pageinfo['content'];
442
443     if ($pagename ==_("InterWikiMap"))
444         $content = _tryinsertInterWikiMap($content);
445
446     $dbi = $request->getDbh();
447     $page = $dbi->getPage($pagename);
448
449     $current = $page->getCurrentRevision();
450     // Try to merge if updated pgsrc contents are different. This
451     // whole thing is hackish
452     //
453     // TODO: try merge unless:
454     // if (current contents = default contents && pgsrc_version >=
455     // pgsrc_version) then just upgrade this pgsrc
456     $needs_merge = false;
457     $merging = false;
458     $overwrite = false;
459
460     if ($request->getArg('merge')) {
461         $merging = true;
462     }
463     else if ($request->getArg('overwrite')) {
464         $overwrite = true;
465     }
466
467     if ( (! $current->hasDefaultContents())
468          && ($current->getPackedContent() != $content)
469          && ($merging == true) ) {
470         include_once('lib/editpage.php');
471         $request->setArg('pagename', $pagename);
472         $r = $current->getVersion();
473         $request->setArg('revision', $current->getVersion());
474         $p = new LoadFileConflictPageEditor($request);
475         $p->_content = $content;
476         $p->_currentVersion = $r - 1;
477         $p->editPage($saveFailed = true);
478         return; //early return
479     }
480
481     foreach ($pagedata as $key => $value) {
482         if (!empty($value))
483             $page->set($key, $value);
484     }
485
486     $mesg = HTML::dd();
487     $skip = false;
488     if ($source)
489         $mesg->pushContent(' ', fmt("from %s", $source));
490
491
492     $current = $page->getCurrentRevision();
493     if ($current->getVersion() == 0) {
494         $mesg->pushContent(' ', _("new page"));
495         $isnew = true;
496     }
497     else {
498         if ( (! $current->hasDefaultContents())
499              && ($current->getPackedContent() != $content) ) {
500             if ($overwrite) {
501                 $mesg->pushContent(' ',
502                                    fmt("has edit conflicts - overwriting anyway"));
503                 $skip = false;
504                 if (substr_count($source, 'pgsrc')) {
505                     $versiondata['author'] = _("The PhpWiki programming team");
506                     // but leave authorid as userid who loaded the file
507                 }
508             }
509             else {
510                 $mesg->pushContent(' ', fmt("has edit conflicts - skipped"));
511                 $needs_merge = true; // hackish
512                 $skip = true;
513             }
514         }
515         else if ($current->getPackedContent() == $content
516                  && $current->get('author') == $versiondata['author']) {
517             // The page metadata is already changed, we don't need a new revision.
518             // This was called previously "is identical to current version %d - skipped"
519             // which is wrong, since the pagedata was stored, not skipped.
520             $mesg->pushContent(' ',
521                                fmt("content is identical to current version %d - no new revision created",
522                                    $current->getVersion()));
523             $skip = true;
524         }
525         $isnew = false;
526     }
527
528     if (! $skip) {
529         $new = $page->save($content, WIKIDB_FORCE_CREATE, $versiondata);
530         $dbi->touch();
531         $mesg->pushContent(' ', fmt("- saved to database as version %d",
532                                     $new->getVersion()));
533     }
534     if ($needs_merge) {
535         $f = $source;
536         // hackish, $source contains needed path+filename
537         $f = str_replace(sprintf(_("MIME file %s"), ''), '', $f);
538         $f = str_replace(sprintf(_("Serialized file %s"), ''), '', $f);
539         $f = str_replace(sprintf(_("plain file %s"), ''), '', $f);
540         //check if uploaded file? they pass just the content, but the file is gone
541         if (@stat($f)) {
542             global $Theme;
543             $meb = Button(array('action' => 'loadfile',
544                                 'merge'=> true,
545                                 'source'=> $f),
546                           _("Merge Edit"),
547                           _("PhpWikiAdministration"),
548                           'wikiadmin');
549             $owb = Button(array('action' => 'loadfile',
550                                 'overwrite'=> true,
551                                 'source'=> $f),
552                           _("Restore Anyway"),
553                           _("PhpWikiAdministration"),
554                           'wikiunsafe');
555             $mesg->pushContent(' ', $meb, " ", $owb);
556         } else {
557             $mesg->pushContent(HTML::em(_(" Sorry, cannot merge uploaded files.")));
558         }
559     }
560
561     if ($skip)
562         PrintXML(HTML::dt(HTML::em(WikiLink($pagename))), $mesg);
563     else
564         PrintXML(HTML::dt(WikiLink($pagename)), $mesg);
565     flush();
566 }
567
568 function _tryinsertInterWikiMap($content) {
569     $goback = false;
570     if (strpos($content, "<verbatim>")) {
571         //$error_html = " The newly loaded pgsrc already contains a verbatim block.";
572         $goback = true;
573     }
574     if (!$goback && !defined('INTERWIKI_MAP_FILE')) {
575         $error_html = sprintf(" "._("%s: not defined"), "INTERWIKI_MAP_FILE");
576         $goback = true;
577     }
578     $mapfile = FindFile(INTERWIKI_MAP_FILE,1);
579     if (!$goback && !file_exists($mapfile)) {
580         $error_html = sprintf(" "._("%s: file not found"), INTERWIKI_MAP_FILE);
581         $goback = true;
582     }
583
584     if (!empty($error_html))
585         trigger_error(_("Default InterWiki map file not loaded.")
586                       . $error_html, E_USER_NOTICE);
587     if ($goback)
588         return $content;
589
590     // if loading from virgin setup do echo, otherwise trigger_error E_USER_NOTICE
591     echo sprintf(_("Loading InterWikiMap from external file %s."), $mapfile),"<br />";
592
593     $fd = fopen ($mapfile, "rb");
594     $data = fread ($fd, filesize($mapfile));
595     fclose ($fd);
596     $content = $content . "\n<verbatim>\n$data</verbatim>\n";
597     return $content;
598 }
599
600 function ParseSerializedPage($text, $default_pagename, $user)
601 {
602     if (!preg_match('/^a:\d+:{[si]:\d+/', $text))
603         return false;
604
605     $pagehash = unserialize($text);
606
607     // Split up pagehash into four parts:
608     //   pagename
609     //   content
610     //   page-level meta-data
611     //   revision-level meta-data
612
613     if (!defined('FLAG_PAGE_LOCKED'))
614         define('FLAG_PAGE_LOCKED', 1);
615     $pageinfo = array('pagedata'    => array(),
616                       'versiondata' => array());
617
618     $pagedata = &$pageinfo['pagedata'];
619     $versiondata = &$pageinfo['versiondata'];
620
621     // Fill in defaults.
622     if (empty($pagehash['pagename']))
623         $pagehash['pagename'] = $default_pagename;
624     if (empty($pagehash['author'])) {
625         $pagehash['author'] = $user->getId();
626     }
627
628     foreach ($pagehash as $key => $value) {
629         switch($key) {
630             case 'pagename':
631             case 'version':
632             case 'hits':
633                 $pageinfo[$key] = $value;
634                 break;
635             case 'content':
636                 $pageinfo[$key] = join("\n", $value);
637                 break;
638             case 'flags':
639                 if (($value & FLAG_PAGE_LOCKED) != 0)
640                     $pagedata['locked'] = 'yes';
641                 break;
642             case 'owner':
643             case 'created':
644                 $pagedata[$key] = $value;
645                 break;
646             case 'acl':
647             case 'perm':
648                 $pagedata['perm'] = ParseMimeifiedPerm($value);
649                 break;
650             case 'lastmodified':
651                 $versiondata['mtime'] = $value;
652                 break;
653             case 'author':
654             case 'author_id':
655             case 'summary':
656                 $versiondata[$key] = $value;
657                 break;
658         }
659     }
660     return $pageinfo;
661 }
662
663 function SortByPageVersion ($a, $b) {
664     return $a['version'] - $b['version'];
665 }
666
667 function LoadFile (&$request, $filename, $text = false, $mtime = false)
668 {
669     if (!is_string($text)) {
670         // Read the file.
671         $stat  = stat($filename);
672         $mtime = $stat[9];
673         $text  = implode("", file($filename));
674     }
675
676     if (! $request->getArg('start_debug'))
677         @set_time_limit(30); // Reset watchdog.
678
679     // FIXME: basename("filewithnoslashes") seems to return garbage sometimes.
680     $basename = basename("/dummy/" . $filename);
681
682     if (!$mtime)
683         $mtime = time();    // Last resort.
684
685     $default_pagename = rawurldecode($basename);
686
687     if ( ($parts = ParseMimeifiedPages($text)) ) {
688         usort($parts, 'SortByPageVersion');
689         foreach ($parts as $pageinfo)
690             SavePage($request, $pageinfo, sprintf(_("MIME file %s"),
691                                                   $filename), $basename);
692     }
693     else if ( ($pageinfo = ParseSerializedPage($text, $default_pagename,
694                                                $request->getUser())) ) {
695         SavePage($request, $pageinfo, sprintf(_("Serialized file %s"),
696                                               $filename), $basename);
697     }
698     else {
699         $user = $request->getUser();
700
701         // Assume plain text file.
702         $pageinfo = array('pagename' => $default_pagename,
703                           'pagedata' => array(),
704                           'versiondata'
705                           => array('author' => $user->getId()),
706                           'content'  => preg_replace('/[ \t\r]*\n/', "\n",
707                                                      chop($text))
708                           );
709         SavePage($request, $pageinfo, sprintf(_("plain file %s"), $filename),
710                  $basename);
711     }
712 }
713
714 function LoadZip (&$request, $zipfile, $files = false, $exclude = false) {
715     $zip = new ZipReader($zipfile);
716     while (list ($fn, $data, $attrib) = $zip->readFile()) {
717         // FIXME: basename("filewithnoslashes") seems to return
718         // garbage sometimes.
719         $fn = basename("/dummy/" . $fn);
720         if ( ($files && !in_array($fn, $files))
721              || ($exclude && in_array($fn, $exclude)) ) {
722             PrintXML(HTML::dt(WikiLink($fn)),
723                      HTML::dd(_("Skipping")));
724             continue;
725         }
726
727         LoadFile($request, $fn, $data, $attrib['mtime']);
728     }
729 }
730
731 function LoadDir (&$request, $dirname, $files = false, $exclude = false) {
732     $fileset = new LimitedFileSet($dirname, $files, $exclude);
733
734     if (!$files and ($skiplist = $fileset->getSkippedFiles())) {
735         PrintXML(HTML::dt(HTML::strong(_("Skipping"))));
736         $list = HTML::ul();
737         foreach ($skiplist as $file)
738             $list->pushContent(HTML::li(WikiLink($file)));
739         PrintXML(HTML::dd($list));
740     }
741
742     // Defer HomePage loading until the end. If anything goes wrong
743     // the pages can still be loaded again.
744     $files = $fileset->getFiles();
745     if (in_array(HOME_PAGE, $files)) {
746         $files = array_diff($files, array(HOME_PAGE));
747         $files[] = HOME_PAGE;
748     }
749     foreach ($files as $file) {
750         if (substr($file,-1,1) != '~') // refuse to load backup files
751             LoadFile($request, "$dirname/$file");
752     }
753 }
754
755 class LimitedFileSet extends FileSet {
756     function LimitedFileSet($dirname, $_include, $exclude) {
757         $this->_includefiles = $_include;
758         $this->_exclude = $exclude;
759         $this->_skiplist = array();
760         parent::FileSet($dirname);
761     }
762
763     function _filenameSelector($fn) {
764         $incl = &$this->_includefiles;
765         $excl = &$this->_exclude;
766
767         if ( ($incl && !in_array($fn, $incl))
768              || ($excl && in_array($fn, $excl)) ) {
769             $this->_skiplist[] = $fn;
770             return false;
771         } else {
772             return true;
773         }
774     }
775
776     function getSkippedFiles () {
777         return $this->_skiplist;
778     }
779 }
780
781
782 function IsZipFile ($filename_or_fd)
783 {
784     // See if it looks like zip file
785     if (is_string($filename_or_fd))
786     {
787         $fd    = fopen($filename_or_fd, "rb");
788         $magic = fread($fd, 4);
789         fclose($fd);
790     }
791     else
792     {
793         $fpos  = ftell($filename_or_fd);
794         $magic = fread($filename_or_fd, 4);
795         fseek($filename_or_fd, $fpos);
796     }
797
798     return $magic == ZIP_LOCHEAD_MAGIC || $magic == ZIP_CENTHEAD_MAGIC;
799 }
800
801
802 function LoadAny (&$request, $file_or_dir, $files = false, $exclude = false)
803 {
804     // Try urlencoded filename for accented characters.
805     if (!file_exists($file_or_dir)) {
806         // Make sure there are slashes first to avoid confusing phps
807         // with broken dirname or basename functions.
808         // FIXME: windows uses \ and :
809         if (is_integer(strpos($file_or_dir, "/"))) {
810             $file_or_dir = FindFile($file_or_dir);
811             // Panic
812             if (!file_exists($file_or_dir))
813                 $file_or_dir = dirname($file_or_dir) . "/"
814                     . urlencode(basename($file_or_dir));
815         } else {
816             // This is probably just a file.
817             $file_or_dir = urlencode($file_or_dir);
818         }
819     }
820
821     $type = filetype($file_or_dir);
822     if ($type == 'link') {
823         // For symbolic links, use stat() to determine
824         // the type of the underlying file.
825         list(,,$mode) = stat($file_or_dir);
826         $type = ($mode >> 12) & 017;
827         if ($type == 010)
828             $type = 'file';
829         elseif ($type == 004)
830             $type = 'dir';
831     }
832
833     if (! $type) {
834         $request->finish(fmt("Unable to load: %s", $file_or_dir));
835     }
836     else if ($type == 'dir') {
837         LoadDir($request, $file_or_dir, $files, $exclude);
838     }
839     else if ($type != 'file' && !preg_match('/^(http|ftp):/', $file_or_dir))
840     {
841         $request->finish(fmt("Bad file type: %s", $type));
842     }
843     else if (IsZipFile($file_or_dir)) {
844         LoadZip($request, $file_or_dir, $files, $exclude);
845     }
846     else /* if (!$files || in_array(basename($file_or_dir), $files)) */
847     {
848         LoadFile($request, $file_or_dir);
849     }
850 }
851
852 function LoadFileOrDir (&$request)
853 {
854     $source = $request->getArg('source');
855     $finder = new FileFinder;
856     $source = $finder->slashifyPath($source);
857     $page = rawurldecode(basename($source));
858     StartLoadDump($request, fmt("Loading '%s'", 
859         HTML(dirname($source),
860              dirname($source) ? "/" : "",
861              WikiLink($page,'auto'))));
862     echo "<dl>\n";
863     LoadAny($request, $source);
864     echo "</dl>\n";
865     EndLoadDump($request);
866 }
867
868 function SetupWiki (&$request)
869 {
870     global $GenericPages, $LANG;
871
872
873     //FIXME: This is a hack (err, "interim solution")
874     // This is a bogo-bogo-login:  Login without
875     // saving login information in session state.
876     // This avoids logging in the unsuspecting
877     // visitor as "The PhpWiki programming team".
878     //
879     // This really needs to be cleaned up...
880     // (I'm working on it.)
881     $real_user = $request->_user;
882     if (ENABLE_USER_NEW)
883         $request->_user = new _BogoUser(_("The PhpWiki programming team"));
884
885     else
886         $request->_user = new WikiUser($request, _("The PhpWiki programming team"),
887                                        WIKIAUTH_BOGO);
888
889     StartLoadDump($request, _("Loading up virgin wiki"));
890     echo "<dl>\n";
891
892     $pgsrc = FindLocalizedFile(WIKI_PGSRC);
893     $default_pgsrc = FindFile(DEFAULT_WIKI_PGSRC);
894
895     $request->setArg('overwrite',true);
896     if ($default_pgsrc != $pgsrc) {
897         LoadAny($request, $default_pgsrc, $GenericPages);
898     }
899     $request->setArg('overwrite',false);
900     LoadAny($request, $pgsrc);
901
902     // Ensure that all mandatory pages are loaded
903     $finder = new FileFinder;
904     foreach (array_merge(explode(':','OldTextFormattingRules:TextFormattingRules:PhpWikiAdministration'),
905                          $GLOBALS['AllActionPages'],
906                          array(constant('HOME_PAGE'))) as $f) {
907         $page = gettext($f);
908         if (isSubPage($page))
909             $page = urlencode($page);
910         if (! $request->_dbi->isWikiPage(urldecode($page)) ) {
911             // translated version provided?
912             if ($lf = FindLocalizedFile($pgsrc . $finder->_pathsep . $page, 1))
913                 LoadAny($request, $lf);
914             else { // load english version of required action page
915                 LoadAny($request, FindFile(DEFAULT_WIKI_PGSRC . $finder->_pathsep . urlencode($f)));
916                 $page = $f;
917             }
918         }
919         if (!$request->_dbi->isWikiPage(urldecode($page))) {
920             trigger_error(sprintf("Mandatory file %s couldn't be loaded!", $page),
921                           E_USER_WARNING);
922         }
923     }
924
925     echo "</dl>\n";
926     EndLoadDump($request);
927 }
928
929 function LoadPostFile (&$request)
930 {
931     $upload = $request->getUploadedFile('file');
932
933     if (!$upload)
934         $request->finish(_("No uploaded file to upload?")); // FIXME: more concise message
935
936
937     // Dump http headers.
938     StartLoadDump($request, sprintf(_("Uploading %s"), $upload->getName()));
939     echo "<dl>\n";
940
941     $fd = $upload->open();
942     if (IsZipFile($fd))
943         LoadZip($request, $fd, false, array(_("RecentChanges")));
944     else
945         LoadFile($request, $upload->getName(), $upload->getContents());
946
947     echo "</dl>\n";
948     EndLoadDump($request);
949 }
950
951 /**
952  $Log: not supported by cvs2svn $
953  Revision 1.105  2004/06/08 19:48:16  rurban
954  fixed foreign setup: no ugly skipped msg for the GenericPages, load english actionpages if translated not found
955
956  Revision 1.104  2004/06/08 13:51:57  rurban
957  some comments only
958
959  Revision 1.103  2004/06/08 10:54:46  rurban
960  better acl dump representation, read back acl and owner
961
962  Revision 1.102  2004/06/06 16:58:51  rurban
963  added more required ActionPages for foreign languages
964  install now english ActionPages if no localized are found. (again)
965  fixed default anon user level to be 0, instead of -1
966    (wrong "required administrator to view this page"...)
967
968  Revision 1.101  2004/06/04 20:32:53  rurban
969  Several locale related improvements suggested by Pierrick Meignen
970  LDAP fix by John Cole
971  reanable admin check without ENABLE_PAGEPERM in the admin plugins
972
973  Revision 1.100  2004/05/02 21:26:38  rurban
974  limit user session data (HomePageHandle and auth_dbi have to invalidated anyway)
975    because they will not survive db sessions, if too large.
976  extended action=upgrade
977  some WikiTranslation button work
978  revert WIKIAUTH_UNOBTAINABLE (need it for main.php)
979  some temp. session debug statements
980
981  Revision 1.99  2004/05/02 15:10:07  rurban
982  new finally reliable way to detect if /index.php is called directly
983    and if to include lib/main.php
984  new global AllActionPages
985  SetupWiki now loads all mandatory pages: HOME_PAGE, action pages, and warns if not.
986  WikiTranslation what=buttons for Carsten to create the missing MacOSX buttons
987  PageGroupTestOne => subpages
988  renamed PhpWikiRss to PhpWikiRecentChanges
989  more docs, default configs, ...
990
991  Revision 1.98  2004/04/29 23:25:12  rurban
992  re-ordered locale init (as in 1.3.9)
993  fixed loadfile with subpages, and merge/restore anyway
994    (sf.net bug #844188)
995
996  Revision 1.96  2004/04/19 23:13:03  zorloc
997  Connect the rest of PhpWiki to the IniConfig system.  Also the keyword regular expression is not a config setting
998
999  Revision 1.95  2004/04/18 01:11:52  rurban
1000  more numeric pagename fixes.
1001  fixed action=upload with merge conflict warnings.
1002  charset changed from constant to global (dynamic utf-8 switching)
1003
1004  Revision 1.94  2004/03/14 16:36:37  rurban
1005  dont load backup files
1006
1007  Revision 1.93  2004/02/26 03:22:05  rurban
1008  also copy css and images with XHTML Dump
1009
1010  Revision 1.92  2004/02/26 02:25:54  rurban
1011  fix empty and #-anchored links in XHTML Dumps
1012
1013  Revision 1.91  2004/02/24 17:19:37  rurban
1014  debugging helpers only
1015
1016  Revision 1.90  2004/02/24 17:09:24  rurban
1017  fixed \r\r\n with dumping on windows
1018
1019  Revision 1.88  2004/02/22 23:20:31  rurban
1020  fixed DumpHtmlToDir,
1021  enhanced sortby handling in PageList
1022    new button_heading th style (enabled),
1023  added sortby and limit support to the db backends and plugins
1024    for paging support (<<prev, next>> links on long lists)
1025
1026  Revision 1.87  2004/01/26 09:17:49  rurban
1027  * changed stored pref representation as before.
1028    the array of objects is 1) bigger and 2)
1029    less portable. If we would import packed pref
1030    objects and the object definition was changed, PHP would fail.
1031    This doesn't happen with an simple array of non-default values.
1032  * use $prefs->retrieve and $prefs->store methods, where retrieve
1033    understands the interim format of array of objects also.
1034  * simplified $prefs->get() and fixed $prefs->set()
1035  * added $user->_userid and class '_WikiUser' portability functions
1036  * fixed $user object ->_level upgrading, mostly using sessions.
1037    this fixes yesterdays problems with loosing authorization level.
1038  * fixed WikiUserNew::checkPass to return the _level
1039  * fixed WikiUserNew::isSignedIn
1040  * added explodePageList to class PageList, support sortby arg
1041  * fixed UserPreferences for WikiUserNew
1042  * fixed WikiPlugin for empty defaults array
1043  * UnfoldSubpages: added pagename arg, renamed pages arg,
1044    removed sort arg, support sortby arg
1045
1046  Revision 1.86  2003/12/02 16:18:26  carstenklapp
1047  Minor enhancement: Provide more meaningful filenames for WikiDB zip
1048  dumps & snapshots.
1049
1050  Revision 1.85  2003/11/30 18:18:13  carstenklapp
1051  Minor code optimization: use include_once instead of require_once
1052  inside functions that might not always called.
1053
1054  Revision 1.84  2003/11/26 20:47:47  carstenklapp
1055  Redo bugfix: My last refactoring broke merge-edit & overwrite
1056  functionality again, should be fixed now. Sorry.
1057
1058  Revision 1.83  2003/11/20 22:18:54  carstenklapp
1059  New feature: h1 during merge-edit displays WikiLink to original page.
1060  Internal changes: Replaced some hackish url-generation code in
1061  function SavePage (for pgsrc merge-edit) with appropriate Button()
1062  calls.
1063
1064  Revision 1.82  2003/11/18 19:48:01  carstenklapp
1065  Fixed missing gettext _() for button name.
1066
1067  Revision 1.81  2003/11/18 18:28:35  carstenklapp
1068  Bugfix: In the Load File function of PhpWikiAdministration: When doing
1069  a "Merge Edit" or "Restore Anyway", page names containing accented
1070  letters (such as locale/de/pgsrc/G%E4steBuch) would produce a file not
1071  found error (Use FilenameForPage funtion to urlencode page names).
1072
1073  Revision 1.80  2003/03/07 02:46:57  dairiki
1074  Omit checks for safe_mode before set_time_limit().  Just prefix the
1075  set_time_limit() calls with @ so that they fail silently if not
1076  supported.
1077
1078  Revision 1.79  2003/02/26 01:56:05  dairiki
1079  Only zip pages with legal pagenames.
1080
1081  Revision 1.78  2003/02/24 02:05:43  dairiki
1082  Fix "n bytes written" message when dumping HTML.
1083
1084  Revision 1.77  2003/02/21 04:12:05  dairiki
1085  Minor fixes for new cached markup.
1086
1087  Revision 1.76  2003/02/16 19:47:17  dairiki
1088  Update WikiDB timestamp when editing or deleting pages.
1089
1090  Revision 1.75  2003/02/15 03:04:30  dairiki
1091  Fix for WikiUser constructor API change.
1092
1093  Revision 1.74  2003/02/15 02:18:04  dairiki
1094  When default language was English (at least), pgsrc was being
1095  loaded twice.
1096
1097  LimitedFileSet: Fix typo/bug. ($include was being ignored.)
1098
1099  SetupWiki(): Fix bugs in loading of $GenericPages.
1100
1101  Revision 1.73  2003/01/28 21:09:17  zorloc
1102  The get_cfg_var() function should only be used when one is
1103  interested in the value from php.ini or similar. Use ini_get()
1104  instead to get the effective value of a configuration variable.
1105  -- Martin Geisler
1106
1107  Revision 1.72  2003/01/03 22:25:53  carstenklapp
1108  Cosmetic fix to "Merge Edit" & "Overwrite" buttons. Added "The PhpWiki
1109  programming team" as author when loading from pgsrc. Source
1110  reformatting.
1111
1112  Revision 1.71  2003/01/03 02:48:05  carstenklapp
1113  function SavePage: Added loadfile options for overwriting or merge &
1114  compare a loaded pgsrc file with an existing page.
1115
1116  function LoadAny: Added a general error message when unable to load a
1117  file instead of defaulting to "Bad file type".
1118
1119  */
1120
1121 // For emacs users
1122 // Local Variables:
1123 // mode: php
1124 // tab-width: 8
1125 // c-basic-offset: 4
1126 // c-hanging-comment-ender-p: nil
1127 // indent-tabs-mode: nil
1128 // End:
1129 ?>