]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/loadsave.php
Added an option to control the suffix appended to filenames in the XHTML file dump...
[SourceForge/phpwiki.git] / lib / loadsave.php
1 <?php rcs_id('$Id: loadsave.php,v 1.65 2002-03-25 20:21:57 carstenklapp Exp $');
2
3 require_once("lib/ziplib.php");
4 require_once("lib/Template.php");
5
6 function StartLoadDump(&$request, $title, $html = '')
7 {
8     // FIXME: This is a hack
9     $tmpl = Template('top', array('TITLE' => $title,
10                                   'HEADER' => $title,
11                                   'CONTENT' => '%BODY%'));
12     echo ereg_replace('%BODY%.*', '', $tmpl->getExpansion($html));
13 }
14
15 function EndLoadDump(&$request)
16 {
17     // FIXME: This is a hack
18     $pagelink = WikiLink($request->getPage());
19
20     PrintXML(HTML::p(HTML::strong(_("Complete."))),
21              HTML::p(fmt("Return to %s", $pagelink)));
22     echo "</body></html>\n";
23 }
24
25
26 ////////////////////////////////////////////////////////////////
27 //
28 //  Functions for dumping.
29 //
30 ////////////////////////////////////////////////////////////////
31
32 /**
33  * For reference see:
34  * http://www.nacs.uci.edu/indiv/ehood/MIME/2045/rfc2045.html
35  * http://www.faqs.org/rfcs/rfc2045.html
36  * (RFC 1521 has been superceeded by RFC 2045 & others).
37  *
38  * Also see http://www.faqs.org/rfcs/rfc2822.html
39  */
40 function MailifyPage ($page, $nversions = 1)
41 {
42     $current = $page->getCurrentRevision();
43     $head = '';
44
45     if (STRICT_MAILABLE_PAGEDUMPS) {
46         $from = defined('SERVER_ADMIN') ? SERVER_ADMIN : 'foo@bar';
47         //This is for unix mailbox format: (not RFC (2)822)
48         // $head .= "From $from  " . CTime(time()) . "\r\n";
49         $head .= "Subject: " . rawurlencode($page->getName()) . "\r\n";
50         $head .= "From: $from (PhpWiki)\r\n";
51         // RFC 2822 requires only a Date: and originator (From:)
52         // field, however the obsolete standard RFC 822 also
53         // requires a destination field.
54         $head .= "To: $from (PhpWiki)\r\n";
55     }
56     $head .= "Date: " . Rfc2822DateTime($current->get('mtime')) . "\r\n";
57     $head .= sprintf("Mime-Version: 1.0 (Produced by PhpWiki %s)\r\n",
58                      PHPWIKI_VERSION);
59
60     // This should just be entered by hand (or by script?)
61     // in the actual pgsrc files, since only they should have
62     // RCS ids.
63     //$head .= "X-Rcs-Id: \$Id\$\r\n";
64
65     $iter = $page->getAllRevisions();
66     $parts = array();
67     while ($revision = $iter->next()) {
68         $parts[] = MimeifyPageRevision($revision);
69         if ($nversions > 0 && count($parts) >= $nversions)
70             break;
71     }
72     if (count($parts) > 1)
73         return $head . MimeMultipart($parts);
74     assert($parts);
75     return $head . $parts[0];
76 }
77
78 /***
79  * Compute filename to used for storing contents of a wiki page.
80  *
81  * Basically we do a rawurlencode() which encodes everything except
82  * ASCII alphanumerics and '.', '-', and '_'.
83  *
84  * But we also want to encode leading dots to avoid filenames like
85  * '.', and '..'. (Also, there's no point in generating "hidden" file
86  * names, like '.foo'.)
87  *
88  * @param $pagename string Pagename.
89  * @return string Filename for page.
90  */
91 function FilenameForPage ($pagename)
92 {
93     $enc = rawurlencode($pagename);
94     return preg_replace('/^\./', '%2e', $enc);
95 }
96
97 /**
98  * The main() function which generates a zip archive of a PhpWiki.
99  *
100  * If $include_archive is false, only the current version of each page
101  * is included in the zip file; otherwise all archived versions are
102  * included as well.
103  */
104 function MakeWikiZip (&$request)
105 {
106     if ($request->getArg('include') == 'all') {
107         $zipname         = "wikidb.zip";
108         $include_archive = true;
109     }
110     else {
111         $zipname         = "wiki.zip";
112         $include_archive = false;
113     }
114
115
116
117     $zip = new ZipWriter("Created by PhpWiki", $zipname);
118
119     $dbi = $request->getDbh();
120     $pages = $dbi->getAllPages();
121     while ($page = $pages->next()) {
122         if (! get_cfg_var('safe_mode'))
123             set_time_limit(30); // Reset watchdog.
124
125         $current = $page->getCurrentRevision();
126         if ($current->getVersion() == 0)
127             continue;
128
129
130         $attrib = array('mtime'    => $current->get('mtime'),
131                         'is_ascii' => 1);
132         if ($page->get('locked'))
133             $attrib['write_protected'] = 1;
134
135         if ($include_archive)
136             $content = MailifyPage($page, 0);
137         else
138             $content = MailifyPage($page);
139
140         $zip->addRegularFile( FilenameForPage($page->getName()),
141                               $content, $attrib);
142     }
143     $zip->finish();
144 }
145
146 function DumpToDir (&$request)
147 {
148     $directory = $request->getArg('directory');
149     if (empty($directory))
150         $request->finish(_("You must specify a directory to dump to"));
151
152     // see if we can access the directory the user wants us to use
153     if (! file_exists($directory)) {
154         if (! mkdir($directory, 0755))
155             $request->finish(fmt("Cannot create directory '%s'", $directory));
156         else
157             $html = HTML::p(fmt("Created directory '%s' for the page dump...",
158                                 $directory));
159     } else {
160         $html = HTML::p(fmt("Using directory '%s'", $directory));
161     }
162
163     StartLoadDump($request, _("Dumping Pages"), $html);
164
165     $dbi = $request->getDbh();
166     $pages = $dbi->getAllPages();
167
168     while ($page = $pages->next()) {
169         if (! get_cfg_var('safe_mode'))
170             set_time_limit(30); // Reset watchdog.
171
172         $filename = FilenameForPage($page->getName());
173
174         $msg = HTML(HTML::br(), $page->getName(), ' ... ');
175
176         if($page->getName() != $filename) {
177             $msg->pushContent(HTML::small(fmt("saved as %s", $filename)),
178                               " ... ");
179         }
180
181         if ($request->getArg('include') == 'all')
182             $data = MailifyPage($page, 0);
183         else
184             $data = MailifyPage($page);
185
186         if ( !($fd = fopen("$directory/$filename", "w")) ) {
187             $msg->pushContent(HTML::strong(fmt("couldn't open file '%s' for writing",
188                                                "$directory/$filename")));
189             $request->finish($msg);
190         }
191
192         $num = fwrite($fd, $data, strlen($data));
193         $msg->pushContent(HTML::small(fmt("%s bytes written", $num)));
194         PrintXML($msg);
195
196         flush();
197         assert($num == strlen($data));
198         fclose($fd);
199     }
200
201     EndLoadDump($request);
202 }
203
204
205 function DumpHtmlToDir (&$request)
206 {
207     $directory = $request->getArg('directory');
208     if (empty($directory))
209         $request->finish(_("You must specify a directory to dump to"));
210
211     // see if we can access the directory the user wants us to use
212     if (! file_exists($directory)) {
213         if (! mkdir($directory, 0755))
214             $request->finish(fmt("Cannot create directory '%s'", $directory));
215         else
216             $html = HTML::p(fmt("Created directory '%s' for the page dump...",
217                                 $directory));
218     } else {
219         $html = HTML::p(fmt("Using directory '%s'", $directory));
220     }
221
222     StartLoadDump($request, _("Dumping Pages"), $html);
223
224     $dbi = $request->getDbh();
225     $pages = $dbi->getAllPages();
226
227     global $HTML_DUMP_SUFFIX, $Theme;
228     if ($HTML_DUMP_SUFFIX)
229         $Theme->HTML_DUMP_SUFFIX = $HTML_DUMP_SUFFIX;
230
231     while ($page = $pages->next()) {
232         if (! get_cfg_var('safe_mode'))
233             set_time_limit(30); // Reset watchdog.
234
235         $filename = FilenameForPage($page->getName()) . $Theme->HTML_DUMP_SUFFIX;
236
237         $msg = HTML(HTML::br(), $page->getName(), ' ... ');
238
239         if($page->getName() != $filename) {
240             $msg->pushContent(HTML::small(fmt("saved as %s", $filename)),
241                               " ... ");
242         }
243
244         $revision = $page->getCurrentRevision();
245
246         require_once('lib/PageType.php');
247         $transformedContent = PageType($revision);
248
249         $template = new Template('browse', $request,
250                                   array('revision' => $revision, 'CONTENT' => $transformedContent));
251
252         $data = GeneratePageasXML($template, $page->getName());
253
254         if ( !($fd = fopen("$directory/$filename", "w")) ) {
255             $msg->pushContent(HTML::strong(fmt("couldn't open file '%s' for writing",
256                                                "$directory/$filename")));
257             $request->finish($msg);
258         }
259
260         $num = fwrite($fd, $data, strlen($data));
261         $msg->pushContent(HTML::small(fmt("%s bytes written\n", $num)));
262         PrintXML($msg);
263
264         flush();
265         assert($num == strlen($data));
266         fclose($fd);
267     }
268
269     //CopyImageFiles() will go here;
270     $Theme->$HTML_DUMP_SUFFIX = '';
271
272     EndLoadDump($request);
273 }
274
275 function MakeWikiZipHtml (&$request)
276 {
277     $zipname = "wikihtml.zip";
278     $zip = new ZipWriter("Created by PhpWiki", $zipname);
279     $dbi = $request->getDbh();
280     $pages = $dbi->getAllPages();
281     while ($page = $pages->next()) {
282         if (! get_cfg_var('safe_mode'))
283             set_time_limit(30); // Reset watchdog.
284
285         $current = $page->getCurrentRevision();
286         if ($current->getVersion() == 0)
287             continue;
288
289         $attrib = array('mtime'    => $current->get('mtime'),
290                         'is_ascii' => 1);
291         if ($page->get('locked'))
292             $attrib['write_protected'] = 1;
293
294         $pagename = $page->getName();
295         $filename = FilenameForPage($pagename);
296         //$filename = $filename . ".html";
297
298         $revision = $page->getCurrentRevision();
299
300         require_once('lib/PageType.php');
301         $transformedContent = PageType($revision);
302
303         $template = new Template('browse', $request,
304                                   array('revision' => $revision,
305                                         'CONTENT' => $transformedContent));
306
307         $data = GeneratePageasXML($template, $pagename);
308
309         $zip->addRegularFile( $filename, $data, $attrib);
310     }
311     // FIXME: Deal with images here.
312     $zip->finish();
313 }
314
315
316 ////////////////////////////////////////////////////////////////
317 //
318 //  Functions for restoring.
319 //
320 ////////////////////////////////////////////////////////////////
321
322 function SavePage (&$request, $pageinfo, $source, $filename)
323 {
324     $pagedata    = $pageinfo['pagedata'];    // Page level meta-data.
325     $versiondata = $pageinfo['versiondata']; // Revision level meta-data.
326
327     if (empty($pageinfo['pagename'])) {
328         PrintXML(HTML::dt(HTML::strong(_("Empty pagename!"))));
329         return;
330     }
331
332     if (empty($versiondata['author_id']))
333         $versiondata['author_id'] = $versiondata['author'];
334
335     $pagename = $pageinfo['pagename'];
336     $content  = $pageinfo['content'];
337
338     if ($pagename ==_("InterWikiMap"))
339         $content = _tryinsertInterWikiMap($content);
340
341     $dbi = $request->getDbh();
342     $page = $dbi->getPage($pagename);
343
344     foreach ($pagedata as $key => $value) {
345         if (!empty($value))
346             $page->set($key, $value);
347     }
348
349     $mesg = HTML::dd();
350     $skip = false;
351     if ($source)
352         $mesg->pushContent(' ', fmt("from %s", $source));
353
354
355     $current = $page->getCurrentRevision();
356     if ($current->getVersion() == 0) {
357         $mesg->pushContent(' ', _("new page"));
358         $isnew = true;
359     }
360     else {
361         if ($current->getPackedContent() == $content
362             && $current->get('author') == $versiondata['author']) {
363             $mesg->pushContent(' ',
364                                fmt("is identical to current version %d - skipped",
365                                    $current->getVersion()));
366             $skip = true;
367         }
368         $isnew = false;
369     }
370
371     if (! $skip) {
372         $new = $page->createRevision(WIKIDB_FORCE_CREATE, $content,
373                                      $versiondata,
374                                      ExtractWikiPageLinks($content));
375
376         $mesg->pushContent(' ', fmt("- saved to database as version %d",
377                                     $new->getVersion()));
378     }
379     if ($skip)
380         PrintXML(HTML::dt(HTML::em(WikiLink($pagename))), $mesg);
381     else
382         PrintXML(HTML::dt(WikiLink($pagename)), $mesg);
383     flush();
384 }
385
386 function _tryinsertInterWikiMap($content) {
387     $goback = false;
388     if (strpos($content, "<verbatim>")) {
389         //$error_html = " The newly loaded pgsrc already contains a verbatim block.";
390         $goback = true;
391     }
392     if (!$goback && !defined('INTERWIKI_MAP_FILE')) {
393         $error_html = sprintf(" "._("%s: not defined"), "INTERWIKI_MAP_FILE");
394         $goback = true;
395     }
396     if (!$goback && !file_exists(INTERWIKI_MAP_FILE)) {
397         $error_html = sprintf(" "._("%s: file not found"), INTERWIKI_MAP_FILE);
398         $goback = true;
399     }
400
401     if (!empty($error_html))
402         trigger_error(_("Default InterWiki map file not loaded.")
403                       . $error_html, E_USER_NOTICE);
404
405     if ($goback)
406         return $content;
407
408     $filename = INTERWIKI_MAP_FILE;
409     trigger_error(sprintf(_("Loading InterWikiMap from external file %s."),
410                           $filename), E_USER_NOTICE);
411
412     $fd = fopen ($filename, "rb");
413     $data = fread ($fd, filesize($filename));
414     fclose ($fd);
415     $content = $content . "\n<verbatim>\n$data</verbatim>\n";
416     return $content;
417 }
418
419 function ParseSerializedPage($text, $default_pagename, $user)
420 {
421     if (!preg_match('/^a:\d+:{[si]:\d+/', $text))
422         return false;
423     
424     $pagehash = unserialize($text);
425     
426     // Split up pagehash into four parts:
427     //   pagename
428     //   content
429     //   page-level meta-data
430     //   revision-level meta-data
431     
432     if (!defined('FLAG_PAGE_LOCKED'))
433         define('FLAG_PAGE_LOCKED', 1);
434     $pageinfo = array('pagedata'    => array(),
435                       'versiondata' => array());
436     
437     $pagedata = &$pageinfo['pagedata'];
438     $versiondata = &$pageinfo['versiondata'];
439     
440     // Fill in defaults.
441     if (empty($pagehash['pagename']))
442         $pagehash['pagename'] = $default_pagename;
443     if (empty($pagehash['author'])) {
444         $pagehash['author'] = $user->getId();
445     }
446     
447     foreach ($pagehash as $key => $value) {
448         switch($key) {
449             case 'pagename':
450             case 'version':
451             case 'hits':
452                 $pageinfo[$key] = $value;
453                 break;
454             case 'content':
455                 $pageinfo[$key] = join("\n", $value);
456                 break;
457             case 'flags':
458                 if (($value & FLAG_PAGE_LOCKED) != 0)
459                     $pagedata['locked'] = 'yes';
460                 break;
461             case 'created':
462                 $pagedata[$key] = $value;
463                 break;
464             case 'lastmodified':
465                 $versiondata['mtime'] = $value;
466                 break;
467             case 'author':
468             case 'author_id':
469             case 'summary':
470                 $versiondata[$key] = $value;
471                 break;
472         }
473     }
474     return $pageinfo;
475 }
476
477 function SortByPageVersion ($a, $b) {
478     return $a['version'] - $b['version'];
479 }
480
481 function LoadFile (&$request, $filename, $text = false, $mtime = false)
482 {
483     if (!is_string($text)) {
484         // Read the file.
485         $stat  = stat($filename);
486         $mtime = $stat[9];
487         $text  = implode("", file($filename));
488     }
489
490     if (! get_cfg_var('safe_mode'))
491         set_time_limit(30);     // Reset watchdog.
492
493     // FIXME: basename("filewithnoslashes") seems to return garbage sometimes.
494     $basename = basename("/dummy/" . $filename);
495
496     if (!$mtime)
497         $mtime = time();        // Last resort.
498
499     $default_pagename = rawurldecode($basename);
500
501     if ( ($parts = ParseMimeifiedPages($text)) ) {
502         usort($parts, 'SortByPageVersion');
503         foreach ($parts as $pageinfo)
504             SavePage($request, $pageinfo, sprintf(_("MIME file %s"),
505                                                   $filename), $basename);
506     }
507     else if ( ($pageinfo = ParseSerializedPage($text, $default_pagename,
508                                                $request->getUser())) ) {
509         SavePage($request, $pageinfo, sprintf(_("Serialized file %s"),
510                                               $filename), $basename);
511     }
512     else {
513         $user = $request->getUser();
514
515         // Assume plain text file.
516         $pageinfo = array('pagename' => $default_pagename,
517                           'pagedata' => array(),
518                           'versiondata'
519                           => array('author' => $user->getId()),
520                           'content'  => preg_replace('/[ \t\r]*\n/', "\n",
521                                                      chop($text))
522                           );
523         SavePage($request, $pageinfo, sprintf(_("plain file %s"), $filename),
524                  $basename);
525     }
526 }
527
528 function LoadZip (&$request, $zipfile, $files = false, $exclude = false) {
529     $zip = new ZipReader($zipfile);
530     while (list ($fn, $data, $attrib) = $zip->readFile()) {
531         // FIXME: basename("filewithnoslashes") seems to return
532         // garbage sometimes.
533         $fn = basename("/dummy/" . $fn);
534         if ( ($files && !in_array($fn, $files)) || ($exclude && in_array($fn, $exclude)) ) {
535
536             PrintXML(HTML::dt(WikiLink($fn)),
537                      HTML::dd(_("Skipping")));
538             continue;
539         }
540
541         LoadFile($request, $fn, $data, $attrib['mtime']);
542     }
543 }
544
545 function LoadDir (&$request, $dirname, $files = false, $exclude = false) {
546     $fileset = new LimitedFileSet($dirname, $files, $exclude);
547
548     if (($skiplist = $fileset->getSkippedFiles())) {
549         PrintXML(HTML::dt(HTML::strong(_("Skipping"))));
550         $list = HTML::ul();
551         foreach ($skiplist as $file)
552             $list->pushContent(HTML::li(WikiLink($file)));
553         PrintXML(HTML::dd($list));
554     }
555
556     // Defer HomePage loading until the end. If anything goes wrong
557     // the pages can still be loaded again.
558     $files = $fileset->getFiles();
559     if (in_array(HomePage, $files)) {
560         $files = array_diff($files, array(HomePage));
561         $files[] = HomePage;
562     }
563     foreach ($files as $file)
564         LoadFile($request, "$dirname/$file");
565 }
566
567 class LimitedFileSet extends FileSet {
568     function LimitedFileSet($dirname, $_include, $exclude) {
569         $this->_includefiles = $_include;
570         $this->_exclude = $exclude;
571         $this->_skiplist = array();
572         parent::FileSet($dirname);
573     }
574
575     function _filenameSelector($fn) {
576         $incl = &$this->_include;
577         $excl = &$this->_exclude;
578
579         if (($incl && !in_array($fn, $incl)) || ($excl && in_array($fn, $excl))) {
580             $this->_skiplist[] = $fn;
581             return false;
582         } else {
583             return true;
584         }
585     }
586
587     function getSkippedFiles () {
588         return $this->_skiplist;
589     }
590 }
591
592
593 function IsZipFile ($filename_or_fd)
594 {
595     // See if it looks like zip file
596     if (is_string($filename_or_fd))
597     {
598         $fd    = fopen($filename_or_fd, "rb");
599         $magic = fread($fd, 4);
600         fclose($fd);
601     }
602     else
603     {
604         $fpos  = ftell($filename_or_fd);
605         $magic = fread($filename_or_fd, 4);
606         fseek($filename_or_fd, $fpos);
607     }
608
609     return $magic == ZIP_LOCHEAD_MAGIC || $magic == ZIP_CENTHEAD_MAGIC;
610 }
611
612
613 function LoadAny (&$request, $file_or_dir, $files = false, $exclude = false)
614 {
615     // Try urlencoded filename for accented characters.
616     if (!file_exists($file_or_dir)) {
617         // Make sure there are slashes first to avoid confusing phps
618         // with broken dirname or basename functions.
619         // FIXME: windows uses \ and :
620         if (is_integer(strpos($file_or_dir, "/"))) {
621             $file_or_dir = dirname($file_or_dir) ."/".
622             urlencode(basename($file_or_dir));
623         } else {
624             // This is probably just a file.
625             $file_or_dir = urlencode($file_or_dir);
626         }
627     }
628
629     $type = filetype($file_or_dir);
630     if ($type == 'link') {
631         // For symbolic links, use stat() to determine
632         // the type of the underlying file.
633         list(,,$mode) = stat($file_or_dir);
634         $type = ($mode >> 12) & 017;
635         if ($type == 010)
636             $type = 'file';
637         elseif ($type == 004)
638             $type = 'dir';
639     }
640
641     if ($type == 'dir') {
642         LoadDir($request, $file_or_dir, $files, $exclude);
643     }
644     else if ($type != 'file' && !preg_match('/^(http|ftp):/', $file_or_dir))
645     {
646         $request->finish(fmt("Bad file type: %s", $type));
647     }
648     else if (IsZipFile($file_or_dir)) {
649         LoadZip($request, $file_or_dir, $files, $exclude);
650     }
651     else /* if (!$files || in_array(basename($file_or_dir), $files)) */
652     {
653         LoadFile($request, $file_or_dir);
654     }
655 }
656
657 function LoadFileOrDir (&$request)
658 {
659     $source = $request->getArg('source');
660     StartLoadDump($request, sprintf(_("Loading '%s'"), $source));
661     echo "<dl>\n";
662     LoadAny($request, $source);
663     echo "</dl>\n";
664     EndLoadDump($request);
665 }
666
667 function SetupWiki (&$request)
668 {
669     global $GenericPages, $LANG;
670
671
672     //FIXME: This is a hack (err, "interim solution")
673     // This is a bogo-bogo-login:  Login without
674     // saving login information in session state.
675     // This avoids logging in the unsuspecting
676     // visitor as "The PhpWiki programming team".
677     //
678     // This really needs to be cleaned up...
679     // (I'm working on it.)
680     $real_user = $request->_user;
681     $request->_user = new WikiUser(_("The PhpWiki programming team"),
682                                    WIKIAUTH_BOGO);
683
684     StartLoadDump($request, _("Loading up virgin wiki"));
685     echo "<dl>\n";
686
687     LoadAny($request, FindLocalizedFile(WIKI_PGSRC));
688     if ($LANG != "C")
689         LoadAny($request, FindFile(DEFAULT_WIKI_PGSRC),
690                 $GenericPages);
691
692     echo "</dl>\n";
693     EndLoadDump($request);
694 }
695
696 function LoadPostFile (&$request)
697 {
698     $upload = $request->getUploadedFile('file');
699
700     if (!$upload)
701         $request->finish(_("No uploaded file to upload?")); // FIXME: more concise message
702
703
704     // Dump http headers.
705     StartLoadDump($request, sprintf(_("Uploading %s"), $upload->getName()));
706     echo "<dl>\n";
707
708     $fd = $upload->open();
709     if (IsZipFile($fd))
710         LoadZip($request, $fd, false, array(_("RecentChanges")));
711     else
712         LoadFile($request, $upload->getName(), $upload->getContents());
713
714     echo "</dl>\n";
715     EndLoadDump($request);
716 }
717
718 // For emacs users
719 // Local Variables:
720 // mode: php
721 // tab-width: 8
722 // c-basic-offset: 4
723 // c-hanging-comment-ender-p: nil
724 // indent-tabs-mode: nil
725 // End:
726 ?>