1 <?php rcs_id('$Id: loadsave.php,v 1.51 2002-01-28 18:49:08 dairiki 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
19 $pagelink = $Theme->LinkExistingWikiWord($request->getArg('pagename'));
21 PrintXML(HTML::p(HTML::strong(_("Complete."))),
22 HTML::p(fmt("Return to %s", $pagelink)));
23 echo "</body></html>\n";
27 ////////////////////////////////////////////////////////////////
29 // Functions for dumping.
31 ////////////////////////////////////////////////////////////////
35 * http://www.nacs.uci.edu/indiv/ehood/MIME/2045/rfc2045.html
36 * http://www.faqs.org/rfcs/rfc2045.html
37 * (RFC 1521 has been superceeded by RFC 2045 & others).
39 * Also see http://www.faqs.org/rfcs/rfc2822.html
41 function MailifyPage ($page, $nversions = 1)
43 $current = $page->getCurrentRevision();
46 if (STRICT_MAILABLE_PAGEDUMPS) {
47 $from = defined('SERVER_ADMIN') ? SERVER_ADMIN : 'foo@bar';
48 //This is for unix mailbox format: (not RFC (2)822)
49 // $head .= "From $from " . CTime(time()) . "\r\n";
50 $head .= "Subject: " . rawurlencode($page->getName()) . "\r\n";
51 $head .= "From: $from (PhpWiki)\r\n";
52 // RFC 2822 requires only a Date: and originator (From:)
53 // field, however the obsolete standard RFC 822 also
54 // requires a destination field.
55 $head .= "To: $from (PhpWiki)\r\n";
57 $head .= "Date: " . Rfc2822DateTime($current->get('mtime')) . "\r\n";
58 $head .= sprintf("Mime-Version: 1.0 (Produced by PhpWiki %s)\r\n",
61 // This should just be entered by hand (or by script?)
62 // in the actual pgsrc files, since only they should have
64 //$head .= "X-Rcs-Id: \$Id\$\r\n";
66 $iter = $page->getAllRevisions();
68 while ($revision = $iter->next()) {
69 $parts[] = MimeifyPageRevision($revision);
70 if ($nversions > 0 && count($parts) >= $nversions)
73 if (count($parts) > 1)
74 return $head . MimeMultipart($parts);
76 return $head . $parts[0];
80 * Compute filename to used for storing contents of a wiki page.
82 * Basically we do a rawurlencode() which encodes everything except
83 * ASCII alphanumerics and '.', '-', and '_'.
85 * But we also want to encode leading dots to avoid filenames like
86 * '.', and '..'. (Also, there's no point in generating "hidden" file
87 * names, like '.foo'.)
89 * @param $pagename string Pagename.
90 * @return string Filename for page.
92 function FilenameForPage ($pagename)
94 $enc = rawurlencode($pagename);
95 return preg_replace('/^\./', '%2e', $enc);
99 * The main() function which generates a zip archive of a PhpWiki.
101 * If $include_archive is false, only the current version of each page
102 * is included in the zip file; otherwise all archived versions are
105 function MakeWikiZip (&$request)
107 if ($request->getArg('include') == 'all') {
108 $zipname = "wikidb.zip";
109 $include_archive = true;
112 $zipname = "wiki.zip";
113 $include_archive = false;
118 $zip = new ZipWriter("Created by PhpWiki", $zipname);
120 $dbi = $request->getDbh();
121 $pages = $dbi->getAllPages();
122 while ($page = $pages->next()) {
123 set_time_limit(30); // Reset watchdog.
125 $current = $page->getCurrentRevision();
126 if ($current->getVersion() == 0)
130 $attrib = array('mtime' => $current->get('mtime'),
132 if ($page->get('locked'))
133 $attrib['write_protected'] = 1;
135 if ($include_archive)
136 $content = MailifyPage($page, 0);
138 $content = MailifyPage($page);
140 $zip->addRegularFile( FilenameForPage($page->getName()),
146 function DumpToDir (&$request)
148 $directory = $request->getArg('directory');
149 if (empty($directory))
150 $request->finish(_("You must specify a directory to dump to"));
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));
157 $html = HTML::p(fmt("Created directory '%s' for the page dump...",
160 $html = HTML::p(fmt("Using directory '%s'", $directory));
163 StartLoadDump($request, _("Dumping Pages"), $html);
165 $dbi = $request->getDbh();
166 $pages = $dbi->getAllPages();
168 while ($page = $pages->next()) {
170 $filename = FilenameForPage($page->getName());
172 $msg = HTML(HTML::br(), $page->getName(), ' ... ');
174 if($page->getName() != $filename) {
175 $msg->pushContent(HTML::small(fmt("saved as %s", $filename)),
179 if ($request->getArg('include') == 'all')
180 $data = MailifyPage($page, 0);
182 $data = MailifyPage($page);
184 if ( !($fd = fopen("$directory/$filename", "w")) ) {
185 $msg->pushContent(HTML::strong(fmt("couldn't open file '%s' for writing",
186 "$directory/$filename")));
187 $request->finish($msg);
190 $num = fwrite($fd, $data, strlen($data));
191 $msg->pushContent(HTML::small(fmt("%s bytes written", $num)));
195 assert($num == strlen($data));
199 EndLoadDump($request);
202 ////////////////////////////////////////////////////////////////
204 // Functions for restoring.
206 ////////////////////////////////////////////////////////////////
208 function SavePage (&$request, $pageinfo, $source, $filename)
210 $pagedata = $pageinfo['pagedata']; // Page level meta-data.
211 $versiondata = $pageinfo['versiondata']; // Revision level meta-data.
213 if (empty($pageinfo['pagename'])) {
214 PrintXML(HTML::dt(HTML::strong(_("Empty pagename!"))));
218 if (empty($versiondata['author_id']))
219 $versiondata['author_id'] = $versiondata['author'];
221 $pagename = $pageinfo['pagename'];
222 $content = $pageinfo['content'];
224 $dbi = $request->getDbh();
225 $page = $dbi->getPage($pagename);
227 foreach ($pagedata as $key => $value) {
229 $page->set($key, $value);
235 $mesg->pushContent(' ', fmt("from %s", $source));
238 $current = $page->getCurrentRevision();
239 if ($current->getVersion() == 0) {
240 $mesg->pushContent(' ', _("new page"));
244 if ($current->getPackedContent() == $content
245 && $current->get('author') == $versiondata['author']) {
246 $mesg->pushContent(' ',
247 fmt("is identical to current version %d - skipped",
248 $current->getVersion()));
255 $new = $page->createRevision(WIKIDB_FORCE_CREATE, $content,
257 ExtractWikiPageLinks($content));
259 $mesg->pushContent(' ', fmt("- saved to database as version %d",
260 $new->getVersion()));
264 $pagelink = $Theme->LinkExistingWikiWord($pagename);
266 PrintXML(HTML::dt($pagelink), $mesg);
270 function ParseSerializedPage($text, $default_pagename, $user)
272 if (!preg_match('/^a:\d+:{[si]:\d+/', $text))
275 $pagehash = unserialize($text);
277 // Split up pagehash into four parts:
280 // page-level meta-data
281 // revision-level meta-data
283 if (!defined('FLAG_PAGE_LOCKED'))
284 define('FLAG_PAGE_LOCKED', 1);
285 $pageinfo = array('pagedata' => array(),
286 'versiondata' => array());
288 $pagedata = &$pageinfo['pagedata'];
289 $versiondata = &$pageinfo['versiondata'];
292 if (empty($pagehash['pagename']))
293 $pagehash['pagename'] = $default_pagename;
294 if (empty($pagehash['author'])) {
295 $pagehash['author'] = $user->getId();
298 foreach ($pagehash as $key => $value) {
302 $pageinfo[$key] = $value;
305 $pageinfo[$key] = join("\n", $value);
307 if (($value & FLAG_PAGE_LOCKED) != 0)
308 $pagedata['locked'] = 'yes';
311 $pagedata[$key] = $value;
314 $versiondata['mtime'] = $value;
317 $versiondata[$key] = $value;
324 function SortByPageVersion ($a, $b) {
325 return $a['version'] - $b['version'];
328 function LoadFile (&$request, $filename, $text = false, $mtime = false)
330 if (!is_string($text)) {
332 $stat = stat($filename);
334 $text = implode("", file($filename));
337 set_time_limit(30); // Reset watchdog.
339 // FIXME: basename("filewithnoslashes") seems to return garbage sometimes.
340 $basename = basename("/dummy/" . $filename);
343 $mtime = time(); // Last resort.
345 $default_pagename = rawurldecode($basename);
347 if ( ($parts = ParseMimeifiedPages($text)) ) {
348 usort($parts, 'SortByPageVersion');
349 foreach ($parts as $pageinfo)
350 SavePage($request, $pageinfo, sprintf(_("MIME file %s"),
351 $filename), $basename);
353 else if ( ($pageinfo = ParseSerializedPage($text, $default_pagename,
354 $request->getUser())) ) {
355 SavePage($request, $pageinfo, sprintf(_("Serialized file %s"),
356 $filename), $basename);
359 $user = $request->getUser();
361 // Assume plain text file.
362 $pageinfo = array('pagename' => $default_pagename,
363 'pagedata' => array(),
365 => array('author' => $user->getId()),
366 'content' => preg_replace('/[ \t\r]*\n/', "\n",
369 SavePage($request, $pageinfo, sprintf(_("plain file %s"), $filename),
374 function LoadZip (&$request, $zipfile, $files = false, $exclude = false) {
375 $zip = new ZipReader($zipfile);
377 while (list ($fn, $data, $attrib) = $zip->readFile()) {
378 // FIXME: basename("filewithnoslashes") seems to return
379 // garbage sometimes.
380 $fn = basename("/dummy/" . $fn);
381 if ( ($files && !in_array($fn, $files)) || ($exclude && in_array($fn, $exclude)) ) {
383 PrintXML(HTML::dt($Theme->LinkExistingWikiWord($fn)),
384 HTML::dd(_("Skipping")));
388 LoadFile($request, $fn, $data, $attrib['mtime']);
392 function LoadDir (&$request, $dirname, $files = false, $exclude = false) {
393 $fileset = new LimitedFileSet($dirname, $files, $exclude);
395 if (($skiplist = $fileset->getSkippedFiles())) {
396 PrintXML(HTML::dt(HTML::strong(_("Skipping"))));
399 foreach ($skiplist as $file)
400 $list->pushContent(HTML::li($Theme->LinkExistingWikiWord($file)));
401 PrintXML(HTML::dd($list));
404 // Defer HomePage loading until the end. If anything goes wrong
405 // the pages can still be loaded again.
406 $files = $fileset->getFiles();
407 if ($home = array_search(HomePage, $files)) {
409 array_push($files, HomePage);
411 foreach ($files as $file)
412 LoadFile($request, "$dirname/$file");
415 class LimitedFileSet extends FileSet {
416 function LimitedFileSet($dirname, $_include, $exclude) {
417 $this->_includefiles = $_include;
418 $this->_exclude = $exclude;
419 $this->_skiplist = array();
420 parent::FileSet($dirname);
423 function _filenameSelector($fn) {
424 $incl = &$this->_include;
425 $excl = &$this->_exclude;
427 if (($incl && !in_array($fn, $incl)) || ($excl && in_array($fn, $excl))) {
428 $this->_skiplist[] = $fn;
435 function getSkippedFiles () {
436 return $this->_skiplist;
441 function IsZipFile ($filename_or_fd)
443 // See if it looks like zip file
444 if (is_string($filename_or_fd))
446 $fd = fopen($filename_or_fd, "rb");
447 $magic = fread($fd, 4);
452 $fpos = ftell($filename_or_fd);
453 $magic = fread($filename_or_fd, 4);
454 fseek($filename_or_fd, $fpos);
457 return $magic == ZIP_LOCHEAD_MAGIC || $magic == ZIP_CENTHEAD_MAGIC;
461 function LoadAny (&$request, $file_or_dir, $files = false, $exclude = false)
463 // Try urlencoded filename for accented characters.
464 if (!file_exists($file_or_dir)) {
465 // Make sure there are slashes first to avoid confusing phps
466 // with broken dirname or basename functions.
467 // FIXME: windows uses \ and :
468 if (is_integer(strpos($file_or_dir, "/"))) {
469 $file_or_dir = dirname($file_or_dir) ."/".
470 urlencode(basename($file_or_dir));
472 // This is probably just a file.
473 $file_or_dir = urlencode($file_or_dir);
477 $type = filetype($file_or_dir);
478 if ($type == 'link') {
479 // For symbolic links, use stat() to determine
480 // the type of the underlying file.
481 list(,,$mode) = stat($file_or_dir);
482 $type = ($mode >> 12) & 017;
485 elseif ($type == 004)
489 if ($type == 'dir') {
490 LoadDir($request, $file_or_dir, $files, $exclude);
492 else if ($type != 'file' && !preg_match('/^(http|ftp):/', $file_or_dir))
494 $request->finish(fmt("Bad file type: %s", $type));
496 else if (IsZipFile($file_or_dir)) {
497 LoadZip($request, $file_or_dir, $files, $exclude);
499 else /* if (!$files || in_array(basename($file_or_dir), $files)) */
501 LoadFile($request, $file_or_dir);
505 function LoadFileOrDir (&$request)
507 $source = $request->getArg('source');
508 StartLoadDump($request, sprintf(_("Loading '%s'"), $source));
510 LoadAny($request, $source);
512 EndLoadDump($request);
515 function SetupWiki (&$request)
517 global $GenericPages, $LANG;
520 //FIXME: This is a hack (err, "interim solution")
521 // This is a bogo-bogo-login: Login without
522 // saving login information in session state.
523 // This avoids logging in the unsuspecting
524 // visitor as "The PhpWiki programming team".
526 // This really needs to be cleaned up...
527 // (I'm working on it.)
528 $real_user = $request->_user;
529 $request->_user = new WikiUser(_("The PhpWiki programming team"),
532 StartLoadDump($request, _("Loading up virgin wiki"));
535 LoadAny($request, FindLocalizedFile(WIKI_PGSRC));
537 LoadAny($request, FindFile(DEFAULT_WIKI_PGSRC),
541 EndLoadDump($request);
544 function LoadPostFile (&$request)
546 $upload = $request->getUploadedFile('file');
549 $request->finish(_("No uploaded file to upload?")); // FIXME: more concise message
552 // Dump http headers.
553 StartLoadDump($request, sprintf(_("Uploading %s"), $upload->getName()));
556 $fd = $upload->open();
558 LoadZip($request, $fd, false, array(_("RecentChanges")));
560 LoadFile($request, $upload->getName(), $upload->getContents());
563 EndLoadDump($request);
571 // c-hanging-comment-ender-p: nil
572 // indent-tabs-mode: nil