1 <?php rcs_id('$Id: loadsave.php,v 1.57 2002-02-20 00:14:40 carstenklapp Exp $');
3 require_once("lib/ziplib.php");
4 require_once("lib/Template.php");
6 function StartLoadDump(&$request, $title, $html = '')
8 // FIXME: This is a hack
9 $tmpl = Template('top', array('TITLE' => $title,
11 'CONTENT' => '%BODY%'));
12 echo ereg_replace('%BODY%.*', '', $tmpl->getExpansion($html));
15 function EndLoadDump(&$request)
17 // FIXME: This is a hack
18 $pagelink = WikiLink($request->getPage());
20 PrintXML(HTML::p(HTML::strong(_("Complete."))),
21 HTML::p(fmt("Return to %s", $pagelink)));
22 echo "</body></html>\n";
26 ////////////////////////////////////////////////////////////////
28 // Functions for dumping.
30 ////////////////////////////////////////////////////////////////
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).
38 * Also see http://www.faqs.org/rfcs/rfc2822.html
40 function MailifyPage ($page, $nversions = 1)
42 $current = $page->getCurrentRevision();
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";
56 $head .= "Date: " . Rfc2822DateTime($current->get('mtime')) . "\r\n";
57 $head .= sprintf("Mime-Version: 1.0 (Produced by PhpWiki %s)\r\n",
60 // This should just be entered by hand (or by script?)
61 // in the actual pgsrc files, since only they should have
63 //$head .= "X-Rcs-Id: \$Id\$\r\n";
65 $iter = $page->getAllRevisions();
67 while ($revision = $iter->next()) {
68 $parts[] = MimeifyPageRevision($revision);
69 if ($nversions > 0 && count($parts) >= $nversions)
72 if (count($parts) > 1)
73 return $head . MimeMultipart($parts);
75 return $head . $parts[0];
79 * Compute filename to used for storing contents of a wiki page.
81 * Basically we do a rawurlencode() which encodes everything except
82 * ASCII alphanumerics and '.', '-', and '_'.
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'.)
88 * @param $pagename string Pagename.
89 * @return string Filename for page.
91 function FilenameForPage ($pagename)
93 $enc = rawurlencode($pagename);
94 return preg_replace('/^\./', '%2e', $enc);
98 * The main() function which generates a zip archive of a PhpWiki.
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
104 function MakeWikiZip (&$request)
106 if ($request->getArg('include') == 'all') {
107 $zipname = "wikidb.zip";
108 $include_archive = true;
111 $zipname = "wiki.zip";
112 $include_archive = false;
117 $zip = new ZipWriter("Created by PhpWiki", $zipname);
119 $dbi = $request->getDbh();
120 $pages = $dbi->getAllPages();
121 while ($page = $pages->next()) {
122 set_time_limit(30); // Reset watchdog.
124 $current = $page->getCurrentRevision();
125 if ($current->getVersion() == 0)
129 $attrib = array('mtime' => $current->get('mtime'),
131 if ($page->get('locked'))
132 $attrib['write_protected'] = 1;
134 if ($include_archive)
135 $content = MailifyPage($page, 0);
137 $content = MailifyPage($page);
139 $zip->addRegularFile( FilenameForPage($page->getName()),
145 function DumpToDir (&$request)
147 $directory = $request->getArg('directory');
148 if (empty($directory))
149 $request->finish(_("You must specify a directory to dump to"));
151 // see if we can access the directory the user wants us to use
152 if (! file_exists($directory)) {
153 if (! mkdir($directory, 0755))
154 $request->finish(fmt("Cannot create directory '%s'", $directory));
156 $html = HTML::p(fmt("Created directory '%s' for the page dump...",
159 $html = HTML::p(fmt("Using directory '%s'", $directory));
162 StartLoadDump($request, _("Dumping Pages"), $html);
164 $dbi = $request->getDbh();
165 $pages = $dbi->getAllPages();
167 while ($page = $pages->next()) {
169 $filename = FilenameForPage($page->getName());
171 $msg = HTML(HTML::br(), $page->getName(), ' ... ');
173 if($page->getName() != $filename) {
174 $msg->pushContent(HTML::small(fmt("saved as %s", $filename)),
178 if ($request->getArg('include') == 'all')
179 $data = MailifyPage($page, 0);
181 $data = MailifyPage($page);
183 if ( !($fd = fopen("$directory/$filename", "w")) ) {
184 $msg->pushContent(HTML::strong(fmt("couldn't open file '%s' for writing",
185 "$directory/$filename")));
186 $request->finish($msg);
189 $num = fwrite($fd, $data, strlen($data));
190 $msg->pushContent(HTML::small(fmt("%s bytes written", $num)));
194 assert($num == strlen($data));
198 EndLoadDump($request);
202 function DumpHtmlToDir (&$request)
204 $directory = $request->getArg('directory');
205 if (empty($directory))
206 $request->finish(_("You must specify a directory to dump to"));
208 // see if we can access the directory the user wants us to use
209 if (! file_exists($directory)) {
210 if (! mkdir($directory, 0755))
211 $request->finish(fmt("Cannot create directory '%s'", $directory));
213 $html = HTML::p(fmt("Created directory '%s' for the page dump...",
216 $html = HTML::p(fmt("Using directory '%s'", $directory));
219 StartLoadDump($request, _("Dumping Pages"), $html);
221 $dbi = $request->getDbh();
222 $pages = $dbi->getAllPages();
224 while ($page = $pages->next()) {
226 $filename = FilenameForPage($page->getName()) . ".html";
228 $msg = HTML(HTML::br(), $page->getName(), ' ... ');
230 if($page->getName() != $filename) {
231 $msg->pushContent(HTML::small(fmt("saved as %s", $filename)),
235 $revision = $page->getCurrentRevision();
237 require_once('lib/PageType.php');
238 $transformedContent = PageType($revision);
240 require_once('lib/Template.php');
241 //Can't get the template to work...
242 //$transformedContent = array('CONTENT' => $transformedContent);
243 //$transformedContent = array_merge($request, array('CONTENT' => $transformedContent));
244 $template = $transformedContent; //this works but no template!!
245 //$template = Template('browse', $transformedContent);
246 $data = GeneratePageasXML($template, $page->getName());
248 if ( !($fd = fopen("$directory/$filename", "w")) ) {
249 $msg->pushContent(HTML::strong(fmt("couldn't open file '%s' for writing",
250 "$directory/$filename")));
251 $request->finish($msg);
254 $num = fwrite($fd, $data, strlen($data));
255 $msg->pushContent(HTML::small(fmt("%s bytes written", $num)));
259 assert($num == strlen($data));
263 EndLoadDump($request);
267 ////////////////////////////////////////////////////////////////
269 // Functions for restoring.
271 ////////////////////////////////////////////////////////////////
273 function SavePage (&$request, $pageinfo, $source, $filename)
275 $pagedata = $pageinfo['pagedata']; // Page level meta-data.
276 $versiondata = $pageinfo['versiondata']; // Revision level meta-data.
278 if (empty($pageinfo['pagename'])) {
279 PrintXML(HTML::dt(HTML::strong(_("Empty pagename!"))));
283 if (empty($versiondata['author_id']))
284 $versiondata['author_id'] = $versiondata['author'];
286 $pagename = $pageinfo['pagename'];
287 $content = $pageinfo['content'];
289 if ($pagename ==_("InterWikiMap"))
290 $content = _tryinsertInterWikiMap($content);
292 $dbi = $request->getDbh();
293 $page = $dbi->getPage($pagename);
295 foreach ($pagedata as $key => $value) {
297 $page->set($key, $value);
303 $mesg->pushContent(' ', fmt("from %s", $source));
306 $current = $page->getCurrentRevision();
307 if ($current->getVersion() == 0) {
308 $mesg->pushContent(' ', _("new page"));
312 if ($current->getPackedContent() == $content
313 && $current->get('author') == $versiondata['author']) {
314 $mesg->pushContent(' ',
315 fmt("is identical to current version %d - skipped",
316 $current->getVersion()));
323 $new = $page->createRevision(WIKIDB_FORCE_CREATE, $content,
325 ExtractWikiPageLinks($content));
327 $mesg->pushContent(' ', fmt("- saved to database as version %d",
328 $new->getVersion()));
331 PrintXML(HTML::dt(WikiLink($pagename)), $mesg);
335 function _tryinsertInterWikiMap($content) {
337 if (strpos($content, "<verbatim>")) {
338 //$error_html = " The newly loaded pgsrc already contains a verbatim block.";
341 if (!$goback && !defined('INTERWIKI_MAP_FILE')) {
342 $error_html = sprintf(" "._("%s: not defined"), "INTERWIKI_MAP_FILE");
345 if (!$goback && !file_exists(INTERWIKI_MAP_FILE)) {
346 $error_html = sprintf(" "._("%s: file not found"), INTERWIKI_MAP_FILE);
350 if (!empty($error_html))
351 trigger_error(_("Default InterWiki map file not loaded.")
352 . $error_html, E_USER_NOTICE);
357 $filename = INTERWIKI_MAP_FILE;
358 trigger_error(sprintf(_("Loading InterWikiMap from external file %s."),
359 $filename), E_USER_NOTICE);
361 $fd = fopen ($filename, "rb");
362 $data = fread ($fd, filesize($filename));
364 $content = $content . "\n<verbatim>\n$data</verbatim>\n";
368 function ParseSerializedPage($text, $default_pagename, $user)
370 if (!preg_match('/^a:\d+:{[si]:\d+/', $text))
373 $pagehash = unserialize($text);
375 // Split up pagehash into four parts:
378 // page-level meta-data
379 // revision-level meta-data
381 if (!defined('FLAG_PAGE_LOCKED'))
382 define('FLAG_PAGE_LOCKED', 1);
383 $pageinfo = array('pagedata' => array(),
384 'versiondata' => array());
386 $pagedata = &$pageinfo['pagedata'];
387 $versiondata = &$pageinfo['versiondata'];
390 if (empty($pagehash['pagename']))
391 $pagehash['pagename'] = $default_pagename;
392 if (empty($pagehash['author'])) {
393 $pagehash['author'] = $user->getId();
396 foreach ($pagehash as $key => $value) {
400 $pageinfo[$key] = $value;
403 $pageinfo[$key] = join("\n", $value);
406 if (($value & FLAG_PAGE_LOCKED) != 0)
407 $pagedata['locked'] = 'yes';
410 $pagedata[$key] = $value;
413 $versiondata['mtime'] = $value;
424 function SortByPageVersion ($a, $b) {
425 return $a['version'] - $b['version'];
428 function LoadFile (&$request, $filename, $text = false, $mtime = false)
430 if (!is_string($text)) {
432 $stat = stat($filename);
434 $text = implode("", file($filename));
437 set_time_limit(30); // Reset watchdog.
439 // FIXME: basename("filewithnoslashes") seems to return garbage sometimes.
440 $basename = basename("/dummy/" . $filename);
443 $mtime = time(); // Last resort.
445 $default_pagename = rawurldecode($basename);
447 if ( ($parts = ParseMimeifiedPages($text)) ) {
448 usort($parts, 'SortByPageVersion');
449 foreach ($parts as $pageinfo)
450 SavePage($request, $pageinfo, sprintf(_("MIME file %s"),
451 $filename), $basename);
453 else if ( ($pageinfo = ParseSerializedPage($text, $default_pagename,
454 $request->getUser())) ) {
455 SavePage($request, $pageinfo, sprintf(_("Serialized file %s"),
456 $filename), $basename);
459 $user = $request->getUser();
461 // Assume plain text file.
462 $pageinfo = array('pagename' => $default_pagename,
463 'pagedata' => array(),
465 => array('author' => $user->getId()),
466 'content' => preg_replace('/[ \t\r]*\n/', "\n",
469 SavePage($request, $pageinfo, sprintf(_("plain file %s"), $filename),
474 function LoadZip (&$request, $zipfile, $files = false, $exclude = false) {
475 $zip = new ZipReader($zipfile);
476 while (list ($fn, $data, $attrib) = $zip->readFile()) {
477 // FIXME: basename("filewithnoslashes") seems to return
478 // garbage sometimes.
479 $fn = basename("/dummy/" . $fn);
480 if ( ($files && !in_array($fn, $files)) || ($exclude && in_array($fn, $exclude)) ) {
482 PrintXML(HTML::dt(WikiLink($fn)),
483 HTML::dd(_("Skipping")));
487 LoadFile($request, $fn, $data, $attrib['mtime']);
491 function LoadDir (&$request, $dirname, $files = false, $exclude = false) {
492 $fileset = new LimitedFileSet($dirname, $files, $exclude);
494 if (($skiplist = $fileset->getSkippedFiles())) {
495 PrintXML(HTML::dt(HTML::strong(_("Skipping"))));
497 foreach ($skiplist as $file)
498 $list->pushContent(HTML::li(WikiLink($file)));
499 PrintXML(HTML::dd($list));
502 // Defer HomePage loading until the end. If anything goes wrong
503 // the pages can still be loaded again.
504 $files = $fileset->getFiles();
505 if ($home = array_search(HomePage, $files)) {
507 array_push($files, HomePage);
509 foreach ($files as $file)
510 LoadFile($request, "$dirname/$file");
513 class LimitedFileSet extends FileSet {
514 function LimitedFileSet($dirname, $_include, $exclude) {
515 $this->_includefiles = $_include;
516 $this->_exclude = $exclude;
517 $this->_skiplist = array();
518 parent::FileSet($dirname);
521 function _filenameSelector($fn) {
522 $incl = &$this->_include;
523 $excl = &$this->_exclude;
525 if (($incl && !in_array($fn, $incl)) || ($excl && in_array($fn, $excl))) {
526 $this->_skiplist[] = $fn;
533 function getSkippedFiles () {
534 return $this->_skiplist;
539 function IsZipFile ($filename_or_fd)
541 // See if it looks like zip file
542 if (is_string($filename_or_fd))
544 $fd = fopen($filename_or_fd, "rb");
545 $magic = fread($fd, 4);
550 $fpos = ftell($filename_or_fd);
551 $magic = fread($filename_or_fd, 4);
552 fseek($filename_or_fd, $fpos);
555 return $magic == ZIP_LOCHEAD_MAGIC || $magic == ZIP_CENTHEAD_MAGIC;
559 function LoadAny (&$request, $file_or_dir, $files = false, $exclude = false)
561 // Try urlencoded filename for accented characters.
562 if (!file_exists($file_or_dir)) {
563 // Make sure there are slashes first to avoid confusing phps
564 // with broken dirname or basename functions.
565 // FIXME: windows uses \ and :
566 if (is_integer(strpos($file_or_dir, "/"))) {
567 $file_or_dir = dirname($file_or_dir) ."/".
568 urlencode(basename($file_or_dir));
570 // This is probably just a file.
571 $file_or_dir = urlencode($file_or_dir);
575 $type = filetype($file_or_dir);
576 if ($type == 'link') {
577 // For symbolic links, use stat() to determine
578 // the type of the underlying file.
579 list(,,$mode) = stat($file_or_dir);
580 $type = ($mode >> 12) & 017;
583 elseif ($type == 004)
587 if ($type == 'dir') {
588 LoadDir($request, $file_or_dir, $files, $exclude);
590 else if ($type != 'file' && !preg_match('/^(http|ftp):/', $file_or_dir))
592 $request->finish(fmt("Bad file type: %s", $type));
594 else if (IsZipFile($file_or_dir)) {
595 LoadZip($request, $file_or_dir, $files, $exclude);
597 else /* if (!$files || in_array(basename($file_or_dir), $files)) */
599 LoadFile($request, $file_or_dir);
603 function LoadFileOrDir (&$request)
605 $source = $request->getArg('source');
606 StartLoadDump($request, sprintf(_("Loading '%s'"), $source));
608 LoadAny($request, $source);
610 EndLoadDump($request);
613 function SetupWiki (&$request)
615 global $GenericPages, $LANG;
618 //FIXME: This is a hack (err, "interim solution")
619 // This is a bogo-bogo-login: Login without
620 // saving login information in session state.
621 // This avoids logging in the unsuspecting
622 // visitor as "The PhpWiki programming team".
624 // This really needs to be cleaned up...
625 // (I'm working on it.)
626 $real_user = $request->_user;
627 $request->_user = new WikiUser(_("The PhpWiki programming team"),
630 StartLoadDump($request, _("Loading up virgin wiki"));
633 LoadAny($request, FindLocalizedFile(WIKI_PGSRC));
635 LoadAny($request, FindFile(DEFAULT_WIKI_PGSRC),
639 EndLoadDump($request);
642 function LoadPostFile (&$request)
644 $upload = $request->getUploadedFile('file');
647 $request->finish(_("No uploaded file to upload?")); // FIXME: more concise message
650 // Dump http headers.
651 StartLoadDump($request, sprintf(_("Uploading %s"), $upload->getName()));
654 $fd = $upload->open();
656 LoadZip($request, $fd, false, array(_("RecentChanges")));
658 LoadFile($request, $upload->getName(), $upload->getContents());
661 EndLoadDump($request);
669 // c-hanging-comment-ender-p: nil
670 // indent-tabs-mode: nil