]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/loadsave.php
(from Patrick Henry) New function to dump html files into a zip file.
[SourceForge/phpwiki.git] / lib / loadsave.php
1 <?php rcs_id('$Id: loadsave.php,v 1.63 2002-02-25 15:57:11 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     while ($page = $pages->next()) {
228         if (! get_cfg_var('safe_mode'))
229             set_time_limit(30); // Reset watchdog.
230
231         $filename = FilenameForPage($page->getName()); /* . ".html";*/
232
233         $msg = HTML(HTML::br(), $page->getName(), ' ... ');
234
235         if($page->getName() != $filename) {
236             $msg->pushContent(HTML::small(fmt("saved as %s", $filename)),
237                               " ... ");
238         }
239
240         $revision = $page->getCurrentRevision();
241
242         require_once('lib/PageType.php');
243         $transformedContent = PageType($revision);
244
245         $template = new Template('browse', $request,
246                                   array('revision' => $revision, 'CONTENT' => $transformedContent));
247
248         $data = GeneratePageasXML($template, $page->getName());
249
250         if ( !($fd = fopen("$directory/$filename", "w")) ) {
251             $msg->pushContent(HTML::strong(fmt("couldn't open file '%s' for writing",
252                                                "$directory/$filename")));
253             $request->finish($msg);
254         }
255
256         $num = fwrite($fd, $data, strlen($data));
257         $msg->pushContent(HTML::small(fmt("%s bytes written", $num)));
258         PrintXML($msg);
259
260         flush();
261         assert($num == strlen($data));
262         fclose($fd);
263     }
264
265     //CopyImageFiles() will go here;
266
267     EndLoadDump($request);
268 }
269
270 function MakeWikiZipHtml (&$request)
271 {
272     $zipname = "wikihtml.zip";
273     $zip = new ZipWriter("Created by PhpWiki", $zipname);
274     $dbi = $request->getDbh();
275     $pages = $dbi->getAllPages();
276     while ($page = $pages->next()) {
277         if (! get_cfg_var('safe_mode'))
278             set_time_limit(30); // Reset watchdog.
279
280         $current = $page->getCurrentRevision();
281         if ($current->getVersion() == 0)
282             continue;
283
284         $attrib = array('mtime'    => $current->get('mtime'),
285                         'is_ascii' => 1);
286         if ($page->get('locked'))
287             $attrib['write_protected'] = 1;
288
289         $pagename = $page->getName();
290         $filename = FilenameForPage($pagename);
291         //$filename = $filename . ".html";
292
293         $revision = $page->getCurrentRevision();
294
295         require_once('lib/PageType.php');
296         $transformedContent = PageType($revision);
297
298         $template = new Template('browse', $request,
299                                   array('revision' => $revision,
300                                         'CONTENT' => $transformedContent));
301
302         $data = GeneratePageasXML($template, $pagename);
303
304         $zip->addRegularFile( $filename, $data, $attrib);
305     }
306     // FIXME: Deal with images here.
307     $zip->finish();
308 }
309
310
311 ////////////////////////////////////////////////////////////////
312 //
313 //  Functions for restoring.
314 //
315 ////////////////////////////////////////////////////////////////
316
317 function SavePage (&$request, $pageinfo, $source, $filename)
318 {
319     $pagedata    = $pageinfo['pagedata'];    // Page level meta-data.
320     $versiondata = $pageinfo['versiondata']; // Revision level meta-data.
321
322     if (empty($pageinfo['pagename'])) {
323         PrintXML(HTML::dt(HTML::strong(_("Empty pagename!"))));
324         return;
325     }
326
327     if (empty($versiondata['author_id']))
328         $versiondata['author_id'] = $versiondata['author'];
329
330     $pagename = $pageinfo['pagename'];
331     $content  = $pageinfo['content'];
332
333     if ($pagename ==_("InterWikiMap"))
334         $content = _tryinsertInterWikiMap($content);
335
336     $dbi = $request->getDbh();
337     $page = $dbi->getPage($pagename);
338
339     foreach ($pagedata as $key => $value) {
340         if (!empty($value))
341             $page->set($key, $value);
342     }
343
344     $mesg = HTML::dd();
345     $skip = false;
346     if ($source)
347         $mesg->pushContent(' ', fmt("from %s", $source));
348
349
350     $current = $page->getCurrentRevision();
351     if ($current->getVersion() == 0) {
352         $mesg->pushContent(' ', _("new page"));
353         $isnew = true;
354     }
355     else {
356         if ($current->getPackedContent() == $content
357             && $current->get('author') == $versiondata['author']) {
358             $mesg->pushContent(' ',
359                                fmt("is identical to current version %d - skipped",
360                                    $current->getVersion()));
361             $skip = true;
362         }
363         $isnew = false;
364     }
365
366     if (! $skip) {
367         $new = $page->createRevision(WIKIDB_FORCE_CREATE, $content,
368                                      $versiondata,
369                                      ExtractWikiPageLinks($content));
370
371         $mesg->pushContent(' ', fmt("- saved to database as version %d",
372                                     $new->getVersion()));
373     }
374
375     PrintXML(HTML::dt(WikiLink($pagename)), $mesg);
376     flush();
377 }
378
379 function _tryinsertInterWikiMap($content) {
380     $goback = false;
381     if (strpos($content, "<verbatim>")) {
382         //$error_html = " The newly loaded pgsrc already contains a verbatim block.";
383         $goback = true;
384     }
385     if (!$goback && !defined('INTERWIKI_MAP_FILE')) {
386         $error_html = sprintf(" "._("%s: not defined"), "INTERWIKI_MAP_FILE");
387         $goback = true;
388     }
389     if (!$goback && !file_exists(INTERWIKI_MAP_FILE)) {
390         $error_html = sprintf(" "._("%s: file not found"), INTERWIKI_MAP_FILE);
391         $goback = true;
392     }
393
394     if (!empty($error_html))
395         trigger_error(_("Default InterWiki map file not loaded.")
396                       . $error_html, E_USER_NOTICE);
397
398     if ($goback)
399         return $content;
400
401     $filename = INTERWIKI_MAP_FILE;
402     trigger_error(sprintf(_("Loading InterWikiMap from external file %s."),
403                           $filename), E_USER_NOTICE);
404
405     $fd = fopen ($filename, "rb");
406     $data = fread ($fd, filesize($filename));
407     fclose ($fd);
408     $content = $content . "\n<verbatim>\n$data</verbatim>\n";
409     return $content;
410 }
411
412 function ParseSerializedPage($text, $default_pagename, $user)
413 {
414     if (!preg_match('/^a:\d+:{[si]:\d+/', $text))
415         return false;
416     
417     $pagehash = unserialize($text);
418     
419     // Split up pagehash into four parts:
420     //   pagename
421     //   content
422     //   page-level meta-data
423     //   revision-level meta-data
424     
425     if (!defined('FLAG_PAGE_LOCKED'))
426         define('FLAG_PAGE_LOCKED', 1);
427     $pageinfo = array('pagedata'    => array(),
428                       'versiondata' => array());
429     
430     $pagedata = &$pageinfo['pagedata'];
431     $versiondata = &$pageinfo['versiondata'];
432     
433     // Fill in defaults.
434     if (empty($pagehash['pagename']))
435         $pagehash['pagename'] = $default_pagename;
436     if (empty($pagehash['author'])) {
437         $pagehash['author'] = $user->getId();
438     }
439     
440     foreach ($pagehash as $key => $value) {
441         switch($key) {
442             case 'pagename':
443             case 'version':
444             case 'hits':
445                 $pageinfo[$key] = $value;
446                 break;
447             case 'content':
448                 $pageinfo[$key] = join("\n", $value);
449                 break;
450             case 'flags':
451                 if (($value & FLAG_PAGE_LOCKED) != 0)
452                     $pagedata['locked'] = 'yes';
453                 break;
454             case 'created':
455                 $pagedata[$key] = $value;
456                 break;
457             case 'lastmodified':
458                 $versiondata['mtime'] = $value;
459                 break;
460             case 'author':
461             case 'author_id':
462             case 'summary':
463                 $versiondata[$key] = $value;
464                 break;
465         }
466     }
467     return $pageinfo;
468 }
469
470 function SortByPageVersion ($a, $b) {
471     return $a['version'] - $b['version'];
472 }
473
474 function LoadFile (&$request, $filename, $text = false, $mtime = false)
475 {
476     if (!is_string($text)) {
477         // Read the file.
478         $stat  = stat($filename);
479         $mtime = $stat[9];
480         $text  = implode("", file($filename));
481     }
482
483     if (! get_cfg_var('safe_mode'))
484         set_time_limit(30);     // Reset watchdog.
485
486     // FIXME: basename("filewithnoslashes") seems to return garbage sometimes.
487     $basename = basename("/dummy/" . $filename);
488
489     if (!$mtime)
490         $mtime = time();        // Last resort.
491
492     $default_pagename = rawurldecode($basename);
493
494     if ( ($parts = ParseMimeifiedPages($text)) ) {
495         usort($parts, 'SortByPageVersion');
496         foreach ($parts as $pageinfo)
497             SavePage($request, $pageinfo, sprintf(_("MIME file %s"),
498                                                   $filename), $basename);
499     }
500     else if ( ($pageinfo = ParseSerializedPage($text, $default_pagename,
501                                                $request->getUser())) ) {
502         SavePage($request, $pageinfo, sprintf(_("Serialized file %s"),
503                                               $filename), $basename);
504     }
505     else {
506         $user = $request->getUser();
507
508         // Assume plain text file.
509         $pageinfo = array('pagename' => $default_pagename,
510                           'pagedata' => array(),
511                           'versiondata'
512                           => array('author' => $user->getId()),
513                           'content'  => preg_replace('/[ \t\r]*\n/', "\n",
514                                                      chop($text))
515                           );
516         SavePage($request, $pageinfo, sprintf(_("plain file %s"), $filename),
517                  $basename);
518     }
519 }
520
521 function LoadZip (&$request, $zipfile, $files = false, $exclude = false) {
522     $zip = new ZipReader($zipfile);
523     while (list ($fn, $data, $attrib) = $zip->readFile()) {
524         // FIXME: basename("filewithnoslashes") seems to return
525         // garbage sometimes.
526         $fn = basename("/dummy/" . $fn);
527         if ( ($files && !in_array($fn, $files)) || ($exclude && in_array($fn, $exclude)) ) {
528
529             PrintXML(HTML::dt(WikiLink($fn)),
530                      HTML::dd(_("Skipping")));
531             continue;
532         }
533
534         LoadFile($request, $fn, $data, $attrib['mtime']);
535     }
536 }
537
538 function LoadDir (&$request, $dirname, $files = false, $exclude = false) {
539     $fileset = new LimitedFileSet($dirname, $files, $exclude);
540
541     if (($skiplist = $fileset->getSkippedFiles())) {
542         PrintXML(HTML::dt(HTML::strong(_("Skipping"))));
543         $list = HTML::ul();
544         foreach ($skiplist as $file)
545             $list->pushContent(HTML::li(WikiLink($file)));
546         PrintXML(HTML::dd($list));
547     }
548
549     // Defer HomePage loading until the end. If anything goes wrong
550     // the pages can still be loaded again.
551     $files = $fileset->getFiles();
552     if (in_array(HomePage, $files)) {
553         $files = array_diff($files, array(HomePage));
554         $files[] = HomePage;
555     }
556     foreach ($files as $file)
557         LoadFile($request, "$dirname/$file");
558 }
559
560 class LimitedFileSet extends FileSet {
561     function LimitedFileSet($dirname, $_include, $exclude) {
562         $this->_includefiles = $_include;
563         $this->_exclude = $exclude;
564         $this->_skiplist = array();
565         parent::FileSet($dirname);
566     }
567
568     function _filenameSelector($fn) {
569         $incl = &$this->_include;
570         $excl = &$this->_exclude;
571
572         if (($incl && !in_array($fn, $incl)) || ($excl && in_array($fn, $excl))) {
573             $this->_skiplist[] = $fn;
574             return false;
575         } else {
576             return true;
577         }
578     }
579
580     function getSkippedFiles () {
581         return $this->_skiplist;
582     }
583 }
584
585
586 function IsZipFile ($filename_or_fd)
587 {
588     // See if it looks like zip file
589     if (is_string($filename_or_fd))
590     {
591         $fd    = fopen($filename_or_fd, "rb");
592         $magic = fread($fd, 4);
593         fclose($fd);
594     }
595     else
596     {
597         $fpos  = ftell($filename_or_fd);
598         $magic = fread($filename_or_fd, 4);
599         fseek($filename_or_fd, $fpos);
600     }
601
602     return $magic == ZIP_LOCHEAD_MAGIC || $magic == ZIP_CENTHEAD_MAGIC;
603 }
604
605
606 function LoadAny (&$request, $file_or_dir, $files = false, $exclude = false)
607 {
608     // Try urlencoded filename for accented characters.
609     if (!file_exists($file_or_dir)) {
610         // Make sure there are slashes first to avoid confusing phps
611         // with broken dirname or basename functions.
612         // FIXME: windows uses \ and :
613         if (is_integer(strpos($file_or_dir, "/"))) {
614             $file_or_dir = dirname($file_or_dir) ."/".
615             urlencode(basename($file_or_dir));
616         } else {
617             // This is probably just a file.
618             $file_or_dir = urlencode($file_or_dir);
619         }
620     }
621
622     $type = filetype($file_or_dir);
623     if ($type == 'link') {
624         // For symbolic links, use stat() to determine
625         // the type of the underlying file.
626         list(,,$mode) = stat($file_or_dir);
627         $type = ($mode >> 12) & 017;
628         if ($type == 010)
629             $type = 'file';
630         elseif ($type == 004)
631             $type = 'dir';
632     }
633
634     if ($type == 'dir') {
635         LoadDir($request, $file_or_dir, $files, $exclude);
636     }
637     else if ($type != 'file' && !preg_match('/^(http|ftp):/', $file_or_dir))
638     {
639         $request->finish(fmt("Bad file type: %s", $type));
640     }
641     else if (IsZipFile($file_or_dir)) {
642         LoadZip($request, $file_or_dir, $files, $exclude);
643     }
644     else /* if (!$files || in_array(basename($file_or_dir), $files)) */
645     {
646         LoadFile($request, $file_or_dir);
647     }
648 }
649
650 function LoadFileOrDir (&$request)
651 {
652     $source = $request->getArg('source');
653     StartLoadDump($request, sprintf(_("Loading '%s'"), $source));
654     echo "<dl>\n";
655     LoadAny($request, $source);
656     echo "</dl>\n";
657     EndLoadDump($request);
658 }
659
660 function SetupWiki (&$request)
661 {
662     global $GenericPages, $LANG;
663
664
665     //FIXME: This is a hack (err, "interim solution")
666     // This is a bogo-bogo-login:  Login without
667     // saving login information in session state.
668     // This avoids logging in the unsuspecting
669     // visitor as "The PhpWiki programming team".
670     //
671     // This really needs to be cleaned up...
672     // (I'm working on it.)
673     $real_user = $request->_user;
674     $request->_user = new WikiUser(_("The PhpWiki programming team"),
675                                    WIKIAUTH_BOGO);
676
677     StartLoadDump($request, _("Loading up virgin wiki"));
678     echo "<dl>\n";
679
680     LoadAny($request, FindLocalizedFile(WIKI_PGSRC));
681     if ($LANG != "C")
682         LoadAny($request, FindFile(DEFAULT_WIKI_PGSRC),
683                 $GenericPages);
684
685     echo "</dl>\n";
686     EndLoadDump($request);
687 }
688
689 function LoadPostFile (&$request)
690 {
691     $upload = $request->getUploadedFile('file');
692
693     if (!$upload)
694         $request->finish(_("No uploaded file to upload?")); // FIXME: more concise message
695
696
697     // Dump http headers.
698     StartLoadDump($request, sprintf(_("Uploading %s"), $upload->getName()));
699     echo "<dl>\n";
700
701     $fd = $upload->open();
702     if (IsZipFile($fd))
703         LoadZip($request, $fd, false, array(_("RecentChanges")));
704     else
705         LoadFile($request, $upload->getName(), $upload->getContents());
706
707     echo "</dl>\n";
708     EndLoadDump($request);
709 }
710
711 // For emacs users
712 // Local Variables:
713 // mode: php
714 // tab-width: 8
715 // c-basic-offset: 4
716 // c-hanging-comment-ender-p: nil
717 // indent-tabs-mode: nil
718 // End:
719 ?>