1 <?php rcs_id('$Id: loadsave.php,v 1.70 2002-09-18 18:34:13 dairiki Exp $');
3 Copyright 1999, 2000, 2001, 2002 $ThePhpWikiProgrammingTeam
5 This file is part of PhpWiki.
7 PhpWiki is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 PhpWiki is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with PhpWiki; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 require_once("lib/ziplib.php");
24 require_once("lib/Template.php");
26 function StartLoadDump(&$request, $title, $html = '')
28 // FIXME: This is a hack
29 $tmpl = Template('html', array('TITLE' => $title,
31 'CONTENT' => '%BODY%'));
32 echo ereg_replace('%BODY%.*', '', $tmpl->getExpansion($html));
35 function EndLoadDump(&$request)
37 // FIXME: This is a hack
38 $pagelink = WikiLink($request->getPage());
40 PrintXML(HTML::p(HTML::strong(_("Complete."))),
41 HTML::p(fmt("Return to %s", $pagelink)));
42 echo "</body></html>\n";
46 ////////////////////////////////////////////////////////////////
48 // Functions for dumping.
50 ////////////////////////////////////////////////////////////////
54 * http://www.nacs.uci.edu/indiv/ehood/MIME/2045/rfc2045.html
55 * http://www.faqs.org/rfcs/rfc2045.html
56 * (RFC 1521 has been superceeded by RFC 2045 & others).
58 * Also see http://www.faqs.org/rfcs/rfc2822.html
60 function MailifyPage ($page, $nversions = 1)
62 $current = $page->getCurrentRevision();
65 if (STRICT_MAILABLE_PAGEDUMPS) {
66 $from = defined('SERVER_ADMIN') ? SERVER_ADMIN : 'foo@bar';
67 //This is for unix mailbox format: (not RFC (2)822)
68 // $head .= "From $from " . CTime(time()) . "\r\n";
69 $head .= "Subject: " . rawurlencode($page->getName()) . "\r\n";
70 $head .= "From: $from (PhpWiki)\r\n";
71 // RFC 2822 requires only a Date: and originator (From:)
72 // field, however the obsolete standard RFC 822 also
73 // requires a destination field.
74 $head .= "To: $from (PhpWiki)\r\n";
76 $head .= "Date: " . Rfc2822DateTime($current->get('mtime')) . "\r\n";
77 $head .= sprintf("Mime-Version: 1.0 (Produced by PhpWiki %s)\r\n",
80 // This should just be entered by hand (or by script?)
81 // in the actual pgsrc files, since only they should have
83 //$head .= "X-Rcs-Id: \$Id\$\r\n";
85 $iter = $page->getAllRevisions();
87 while ($revision = $iter->next()) {
88 $parts[] = MimeifyPageRevision($revision);
89 if ($nversions > 0 && count($parts) >= $nversions)
92 if (count($parts) > 1)
93 return $head . MimeMultipart($parts);
95 return $head . $parts[0];
99 * Compute filename to used for storing contents of a wiki page.
101 * Basically we do a rawurlencode() which encodes everything except
102 * ASCII alphanumerics and '.', '-', and '_'.
104 * But we also want to encode leading dots to avoid filenames like
105 * '.', and '..'. (Also, there's no point in generating "hidden" file
106 * names, like '.foo'.)
108 * @param $pagename string Pagename.
109 * @return string Filename for page.
111 function FilenameForPage ($pagename)
113 $enc = rawurlencode($pagename);
114 return preg_replace('/^\./', '%2e', $enc);
118 * The main() function which generates a zip archive of a PhpWiki.
120 * If $include_archive is false, only the current version of each page
121 * is included in the zip file; otherwise all archived versions are
124 function MakeWikiZip (&$request)
126 if ($request->getArg('include') == 'all') {
127 $zipname = "wikidb.zip";
128 $include_archive = true;
131 $zipname = "wiki.zip";
132 $include_archive = false;
137 $zip = new ZipWriter("Created by PhpWiki " . PHPWIKI_VERSION, $zipname);
139 $dbi = $request->getDbh();
140 $pages = $dbi->getAllPages();
141 while ($page = $pages->next()) {
142 if (! get_cfg_var('safe_mode'))
143 set_time_limit(30); // Reset watchdog.
145 $current = $page->getCurrentRevision();
146 if ($current->getVersion() == 0)
150 $attrib = array('mtime' => $current->get('mtime'),
152 if ($page->get('locked'))
153 $attrib['write_protected'] = 1;
155 if ($include_archive)
156 $content = MailifyPage($page, 0);
158 $content = MailifyPage($page);
160 $zip->addRegularFile( FilenameForPage($page->getName()),
166 function DumpToDir (&$request)
168 $directory = $request->getArg('directory');
169 if (empty($directory))
170 $request->finish(_("You must specify a directory to dump to"));
172 // see if we can access the directory the user wants us to use
173 if (! file_exists($directory)) {
174 if (! mkdir($directory, 0755))
175 $request->finish(fmt("Cannot create directory '%s'", $directory));
177 $html = HTML::p(fmt("Created directory '%s' for the page dump...",
180 $html = HTML::p(fmt("Using directory '%s'", $directory));
183 StartLoadDump($request, _("Dumping Pages"), $html);
185 $dbi = $request->getDbh();
186 $pages = $dbi->getAllPages();
188 while ($page = $pages->next()) {
189 if (! get_cfg_var('safe_mode'))
190 set_time_limit(30); // Reset watchdog.
192 $filename = FilenameForPage($page->getName());
194 $msg = HTML(HTML::br(), $page->getName(), ' ... ');
196 if($page->getName() != $filename) {
197 $msg->pushContent(HTML::small(fmt("saved as %s", $filename)),
201 if ($request->getArg('include') == 'all')
202 $data = MailifyPage($page, 0);
204 $data = MailifyPage($page);
206 if ( !($fd = fopen("$directory/$filename", "w")) ) {
207 $msg->pushContent(HTML::strong(fmt("couldn't open file '%s' for writing",
208 "$directory/$filename")));
209 $request->finish($msg);
212 $num = fwrite($fd, $data, strlen($data));
213 $msg->pushContent(HTML::small(fmt("%s bytes written", $num)));
217 assert($num == strlen($data));
221 EndLoadDump($request);
225 function DumpHtmlToDir (&$request)
227 $directory = $request->getArg('directory');
228 if (empty($directory))
229 $request->finish(_("You must specify a directory to dump to"));
231 // see if we can access the directory the user wants us to use
232 if (! file_exists($directory)) {
233 if (! mkdir($directory, 0755))
234 $request->finish(fmt("Cannot create directory '%s'", $directory));
236 $html = HTML::p(fmt("Created directory '%s' for the page dump...",
239 $html = HTML::p(fmt("Using directory '%s'", $directory));
242 StartLoadDump($request, _("Dumping Pages"), $html);
244 $dbi = $request->getDbh();
245 $pages = $dbi->getAllPages();
247 global $HTML_DUMP_SUFFIX, $Theme;
248 if ($HTML_DUMP_SUFFIX)
249 $Theme->HTML_DUMP_SUFFIX = $HTML_DUMP_SUFFIX;
251 while ($page = $pages->next()) {
252 if (! get_cfg_var('safe_mode'))
253 set_time_limit(30); // Reset watchdog.
255 $pagename = $page->getName();
256 $filename = FilenameForPage($pagename) . $Theme->HTML_DUMP_SUFFIX;
258 $msg = HTML(HTML::br(), $pagename, ' ... ');
260 if($page->getName() != $filename) {
261 $msg->pushContent(HTML::small(fmt("saved as %s", $filename)),
265 $revision = $page->getCurrentRevision();
267 require_once('lib/PageType.php');
268 $transformedContent = PageType($revision);
270 $template = new Template('browse', $request,
271 array('revision' => $revision,
272 'CONTENT' => $transformedContent));
274 $data = GeneratePageasXML($template, $pagename);
276 if ( !($fd = fopen("$directory/$filename", "w")) ) {
277 $msg->pushContent(HTML::strong(fmt("couldn't open file '%s' for writing",
278 "$directory/$filename")));
279 $request->finish($msg);
282 $num = fwrite($fd, $data, strlen($data));
283 $msg->pushContent(HTML::small(fmt("%s bytes written", $num) . "\n"));
287 assert($num == strlen($data));
291 //CopyImageFiles() will go here;
292 $Theme->$HTML_DUMP_SUFFIX = '';
294 EndLoadDump($request);
297 /* Known problem: any plugins or other code which echo()s text will
298 * lead to a corrupted html zip file which may produce the following
299 * errors upon unzipping:
301 * warning [wikihtml.zip]: 2401 extra bytes at beginning or within zipfile
302 * file #58: bad zipfile offset (local header sig): 177561
303 * (attempting to re-compensate)
305 * However, the actual wiki page data should be unaffected.
307 function MakeWikiZipHtml (&$request)
309 $zipname = "wikihtml.zip";
310 $zip = new ZipWriter("Created by PhpWiki " . PHPWIKI_VERSION, $zipname);
311 $dbi = $request->getDbh();
312 $pages = $dbi->getAllPages();
314 global $HTML_DUMP_SUFFIX, $Theme;
315 if ($HTML_DUMP_SUFFIX)
316 $Theme->HTML_DUMP_SUFFIX = $HTML_DUMP_SUFFIX;
318 while ($page = $pages->next()) {
319 if (! get_cfg_var('safe_mode'))
320 set_time_limit(30); // Reset watchdog.
322 $current = $page->getCurrentRevision();
323 if ($current->getVersion() == 0)
326 $attrib = array('mtime' => $current->get('mtime'),
328 if ($page->get('locked'))
329 $attrib['write_protected'] = 1;
331 $pagename = $page->getName();
332 $filename = FilenameForPage($pagename) . $Theme->HTML_DUMP_SUFFIX;
333 $revision = $page->getCurrentRevision();
335 require_once('lib/PageType.php');
336 $transformedContent = PageType($revision);
338 $template = new Template('browse', $request,
339 array('revision' => $revision,
340 'CONTENT' => $transformedContent));
342 $data = GeneratePageasXML($template, $pagename);
344 $zip->addRegularFile( $filename, $data, $attrib);
346 // FIXME: Deal with images here.
348 $Theme->$HTML_DUMP_SUFFIX = '';
352 ////////////////////////////////////////////////////////////////
354 // Functions for restoring.
356 ////////////////////////////////////////////////////////////////
358 function SavePage (&$request, $pageinfo, $source, $filename)
360 $pagedata = $pageinfo['pagedata']; // Page level meta-data.
361 $versiondata = $pageinfo['versiondata']; // Revision level meta-data.
363 if (empty($pageinfo['pagename'])) {
364 PrintXML(HTML::dt(HTML::strong(_("Empty pagename!"))));
368 if (empty($versiondata['author_id']))
369 $versiondata['author_id'] = $versiondata['author'];
371 $pagename = $pageinfo['pagename'];
372 $content = $pageinfo['content'];
374 if ($pagename ==_("InterWikiMap"))
375 $content = _tryinsertInterWikiMap($content);
377 $dbi = $request->getDbh();
378 $page = $dbi->getPage($pagename);
380 foreach ($pagedata as $key => $value) {
382 $page->set($key, $value);
388 $mesg->pushContent(' ', fmt("from %s", $source));
391 $current = $page->getCurrentRevision();
392 if ($current->getVersion() == 0) {
393 $mesg->pushContent(' ', _("new page"));
397 if ($current->getPackedContent() == $content
398 && $current->get('author') == $versiondata['author']) {
399 $mesg->pushContent(' ',
400 fmt("is identical to current version %d - skipped",
401 $current->getVersion()));
408 $new = $page->createRevision(WIKIDB_FORCE_CREATE, $content,
410 ExtractWikiPageLinks($content));
412 $mesg->pushContent(' ', fmt("- saved to database as version %d",
413 $new->getVersion()));
416 PrintXML(HTML::dt(HTML::em(WikiLink($pagename))), $mesg);
418 PrintXML(HTML::dt(WikiLink($pagename)), $mesg);
422 function _tryinsertInterWikiMap($content) {
424 if (strpos($content, "<verbatim>")) {
425 //$error_html = " The newly loaded pgsrc already contains a verbatim block.";
428 if (!$goback && !defined('INTERWIKI_MAP_FILE')) {
429 $error_html = sprintf(" "._("%s: not defined"), "INTERWIKI_MAP_FILE");
432 if (!$goback && !file_exists(INTERWIKI_MAP_FILE)) {
433 $error_html = sprintf(" "._("%s: file not found"), INTERWIKI_MAP_FILE);
437 if (!empty($error_html))
438 trigger_error(_("Default InterWiki map file not loaded.")
439 . $error_html, E_USER_NOTICE);
444 $filename = INTERWIKI_MAP_FILE;
445 trigger_error(sprintf(_("Loading InterWikiMap from external file %s."),
446 $filename), E_USER_NOTICE);
448 $fd = fopen ($filename, "rb");
449 $data = fread ($fd, filesize($filename));
451 $content = $content . "\n<verbatim>\n$data</verbatim>\n";
455 function ParseSerializedPage($text, $default_pagename, $user)
457 if (!preg_match('/^a:\d+:{[si]:\d+/', $text))
460 $pagehash = unserialize($text);
462 // Split up pagehash into four parts:
465 // page-level meta-data
466 // revision-level meta-data
468 if (!defined('FLAG_PAGE_LOCKED'))
469 define('FLAG_PAGE_LOCKED', 1);
470 $pageinfo = array('pagedata' => array(),
471 'versiondata' => array());
473 $pagedata = &$pageinfo['pagedata'];
474 $versiondata = &$pageinfo['versiondata'];
477 if (empty($pagehash['pagename']))
478 $pagehash['pagename'] = $default_pagename;
479 if (empty($pagehash['author'])) {
480 $pagehash['author'] = $user->getId();
483 foreach ($pagehash as $key => $value) {
488 $pageinfo[$key] = $value;
491 $pageinfo[$key] = join("\n", $value);
494 if (($value & FLAG_PAGE_LOCKED) != 0)
495 $pagedata['locked'] = 'yes';
498 $pagedata[$key] = $value;
501 $versiondata['mtime'] = $value;
506 $versiondata[$key] = $value;
513 function SortByPageVersion ($a, $b) {
514 return $a['version'] - $b['version'];
517 function LoadFile (&$request, $filename, $text = false, $mtime = false)
519 if (!is_string($text)) {
521 $stat = stat($filename);
523 $text = implode("", file($filename));
526 if (! get_cfg_var('safe_mode'))
527 set_time_limit(30); // Reset watchdog.
529 // FIXME: basename("filewithnoslashes") seems to return garbage sometimes.
530 $basename = basename("/dummy/" . $filename);
533 $mtime = time(); // Last resort.
535 $default_pagename = rawurldecode($basename);
537 if ( ($parts = ParseMimeifiedPages($text)) ) {
538 usort($parts, 'SortByPageVersion');
539 foreach ($parts as $pageinfo)
540 SavePage($request, $pageinfo, sprintf(_("MIME file %s"),
541 $filename), $basename);
543 else if ( ($pageinfo = ParseSerializedPage($text, $default_pagename,
544 $request->getUser())) ) {
545 SavePage($request, $pageinfo, sprintf(_("Serialized file %s"),
546 $filename), $basename);
549 $user = $request->getUser();
551 // Assume plain text file.
552 $pageinfo = array('pagename' => $default_pagename,
553 'pagedata' => array(),
555 => array('author' => $user->getId()),
556 'content' => preg_replace('/[ \t\r]*\n/', "\n",
559 SavePage($request, $pageinfo, sprintf(_("plain file %s"), $filename),
564 function LoadZip (&$request, $zipfile, $files = false, $exclude = false) {
565 $zip = new ZipReader($zipfile);
566 while (list ($fn, $data, $attrib) = $zip->readFile()) {
567 // FIXME: basename("filewithnoslashes") seems to return
568 // garbage sometimes.
569 $fn = basename("/dummy/" . $fn);
570 if ( ($files && !in_array($fn, $files)) || ($exclude && in_array($fn, $exclude)) ) {
572 PrintXML(HTML::dt(WikiLink($fn)),
573 HTML::dd(_("Skipping")));
577 LoadFile($request, $fn, $data, $attrib['mtime']);
581 function LoadDir (&$request, $dirname, $files = false, $exclude = false) {
582 $fileset = new LimitedFileSet($dirname, $files, $exclude);
584 if (($skiplist = $fileset->getSkippedFiles())) {
585 PrintXML(HTML::dt(HTML::strong(_("Skipping"))));
587 foreach ($skiplist as $file)
588 $list->pushContent(HTML::li(WikiLink($file)));
589 PrintXML(HTML::dd($list));
592 // Defer HomePage loading until the end. If anything goes wrong
593 // the pages can still be loaded again.
594 $files = $fileset->getFiles();
595 if (in_array(HOME_PAGE, $files)) {
596 $files = array_diff($files, array(HOME_PAGE));
597 $files[] = HOME_PAGE;
599 foreach ($files as $file)
600 LoadFile($request, "$dirname/$file");
603 class LimitedFileSet extends FileSet {
604 function LimitedFileSet($dirname, $_include, $exclude) {
605 $this->_includefiles = $_include;
606 $this->_exclude = $exclude;
607 $this->_skiplist = array();
608 parent::FileSet($dirname);
611 function _filenameSelector($fn) {
612 $incl = &$this->_include;
613 $excl = &$this->_exclude;
615 if (($incl && !in_array($fn, $incl)) || ($excl && in_array($fn, $excl))) {
616 $this->_skiplist[] = $fn;
623 function getSkippedFiles () {
624 return $this->_skiplist;
629 function IsZipFile ($filename_or_fd)
631 // See if it looks like zip file
632 if (is_string($filename_or_fd))
634 $fd = fopen($filename_or_fd, "rb");
635 $magic = fread($fd, 4);
640 $fpos = ftell($filename_or_fd);
641 $magic = fread($filename_or_fd, 4);
642 fseek($filename_or_fd, $fpos);
645 return $magic == ZIP_LOCHEAD_MAGIC || $magic == ZIP_CENTHEAD_MAGIC;
649 function LoadAny (&$request, $file_or_dir, $files = false, $exclude = false)
651 // Try urlencoded filename for accented characters.
652 if (!file_exists($file_or_dir)) {
653 // Make sure there are slashes first to avoid confusing phps
654 // with broken dirname or basename functions.
655 // FIXME: windows uses \ and :
656 if (is_integer(strpos($file_or_dir, "/"))) {
657 $file_or_dir = FindFile($file_or_dir);
659 if (!file_exists($file_or_dir))
660 $file_or_dir = dirname($file_or_dir) ."/".urlencode(basename($file_or_dir));
662 // This is probably just a file.
663 $file_or_dir = urlencode($file_or_dir);
667 $type = filetype($file_or_dir);
668 if ($type == 'link') {
669 // For symbolic links, use stat() to determine
670 // the type of the underlying file.
671 list(,,$mode) = stat($file_or_dir);
672 $type = ($mode >> 12) & 017;
675 elseif ($type == 004)
679 if ($type == 'dir') {
680 LoadDir($request, $file_or_dir, $files, $exclude);
682 else if ($type != 'file' && !preg_match('/^(http|ftp):/', $file_or_dir))
684 $request->finish(fmt("Bad file type: %s", $type));
686 else if (IsZipFile($file_or_dir)) {
687 LoadZip($request, $file_or_dir, $files, $exclude);
689 else /* if (!$files || in_array(basename($file_or_dir), $files)) */
691 LoadFile($request, $file_or_dir);
695 function LoadFileOrDir (&$request)
697 $source = $request->getArg('source');
698 StartLoadDump($request, sprintf(_("Loading '%s'"), $source));
700 LoadAny($request, $source);
702 EndLoadDump($request);
705 function SetupWiki (&$request)
707 global $GenericPages, $LANG;
710 //FIXME: This is a hack (err, "interim solution")
711 // This is a bogo-bogo-login: Login without
712 // saving login information in session state.
713 // This avoids logging in the unsuspecting
714 // visitor as "The PhpWiki programming team".
716 // This really needs to be cleaned up...
717 // (I'm working on it.)
718 $real_user = $request->_user;
719 $request->_user = new WikiUser(_("The PhpWiki programming team"),
722 StartLoadDump($request, _("Loading up virgin wiki"));
725 LoadAny($request, FindLocalizedFile(WIKI_PGSRC));
726 if ($LANG != "C" and $LANG != "en")
727 LoadAny($request, FindFile(DEFAULT_WIKI_PGSRC),
731 EndLoadDump($request);
734 function LoadPostFile (&$request)
736 $upload = $request->getUploadedFile('file');
739 $request->finish(_("No uploaded file to upload?")); // FIXME: more concise message
742 // Dump http headers.
743 StartLoadDump($request, sprintf(_("Uploading %s"), $upload->getName()));
746 $fd = $upload->open();
748 LoadZip($request, $fd, false, array(_("RecentChanges")));
750 LoadFile($request, $upload->getName(), $upload->getContents());
753 EndLoadDump($request);
761 // c-hanging-comment-ender-p: nil
762 // indent-tabs-mode: nil