2 rcs_id('$Id: loadsave.php,v 1.74 2003-02-15 02:18:04 dairiki Exp $');
5 Copyright 1999, 2000, 2001, 2002 $ThePhpWikiProgrammingTeam
7 This file is part of PhpWiki.
9 PhpWiki is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 PhpWiki is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with PhpWiki; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 require_once("lib/ziplib.php");
26 require_once("lib/Template.php");
28 function StartLoadDump(&$request, $title, $html = '')
30 // FIXME: This is a hack
31 $tmpl = Template('html', array('TITLE' => $title,
33 'CONTENT' => '%BODY%'));
34 echo ereg_replace('%BODY%.*', '', $tmpl->getExpansion($html));
37 function EndLoadDump(&$request)
39 // FIXME: This is a hack
40 $pagelink = WikiLink($request->getPage());
42 PrintXML(HTML::p(HTML::strong(_("Complete."))),
43 HTML::p(fmt("Return to %s", $pagelink)));
44 echo "</body></html>\n";
48 ////////////////////////////////////////////////////////////////
50 // Functions for dumping.
52 ////////////////////////////////////////////////////////////////
56 * http://www.nacs.uci.edu/indiv/ehood/MIME/2045/rfc2045.html
57 * http://www.faqs.org/rfcs/rfc2045.html
58 * (RFC 1521 has been superceeded by RFC 2045 & others).
60 * Also see http://www.faqs.org/rfcs/rfc2822.html
62 function MailifyPage ($page, $nversions = 1)
64 $current = $page->getCurrentRevision();
67 if (STRICT_MAILABLE_PAGEDUMPS) {
68 $from = defined('SERVER_ADMIN') ? SERVER_ADMIN : 'foo@bar';
69 //This is for unix mailbox format: (not RFC (2)822)
70 // $head .= "From $from " . CTime(time()) . "\r\n";
71 $head .= "Subject: " . rawurlencode($page->getName()) . "\r\n";
72 $head .= "From: $from (PhpWiki)\r\n";
73 // RFC 2822 requires only a Date: and originator (From:)
74 // field, however the obsolete standard RFC 822 also
75 // requires a destination field.
76 $head .= "To: $from (PhpWiki)\r\n";
78 $head .= "Date: " . Rfc2822DateTime($current->get('mtime')) . "\r\n";
79 $head .= sprintf("Mime-Version: 1.0 (Produced by PhpWiki %s)\r\n",
82 // This should just be entered by hand (or by script?)
83 // in the actual pgsrc files, since only they should have
85 //$head .= "X-Rcs-Id: \$Id\$\r\n";
87 $iter = $page->getAllRevisions();
89 while ($revision = $iter->next()) {
90 $parts[] = MimeifyPageRevision($revision);
91 if ($nversions > 0 && count($parts) >= $nversions)
94 if (count($parts) > 1)
95 return $head . MimeMultipart($parts);
97 return $head . $parts[0];
101 * Compute filename to used for storing contents of a wiki page.
103 * Basically we do a rawurlencode() which encodes everything except
104 * ASCII alphanumerics and '.', '-', and '_'.
106 * But we also want to encode leading dots to avoid filenames like
107 * '.', and '..'. (Also, there's no point in generating "hidden" file
108 * names, like '.foo'.)
110 * @param $pagename string Pagename.
111 * @return string Filename for page.
113 function FilenameForPage ($pagename)
115 $enc = rawurlencode($pagename);
116 return preg_replace('/^\./', '%2e', $enc);
120 * The main() function which generates a zip archive of a PhpWiki.
122 * If $include_archive is false, only the current version of each page
123 * is included in the zip file; otherwise all archived versions are
126 function MakeWikiZip (&$request)
128 if ($request->getArg('include') == 'all') {
129 $zipname = "wikidb.zip";
130 $include_archive = true;
133 $zipname = "wiki.zip";
134 $include_archive = false;
139 $zip = new ZipWriter("Created by PhpWiki " . PHPWIKI_VERSION, $zipname);
141 $dbi = $request->getDbh();
142 $pages = $dbi->getAllPages();
143 while ($page = $pages->next()) {
144 if (! ini_get('safe_mode'))
145 set_time_limit(30); // Reset watchdog.
147 $current = $page->getCurrentRevision();
148 if ($current->getVersion() == 0)
152 $attrib = array('mtime' => $current->get('mtime'),
154 if ($page->get('locked'))
155 $attrib['write_protected'] = 1;
157 if ($include_archive)
158 $content = MailifyPage($page, 0);
160 $content = MailifyPage($page);
162 $zip->addRegularFile( FilenameForPage($page->getName()),
168 function DumpToDir (&$request)
170 $directory = $request->getArg('directory');
171 if (empty($directory))
172 $request->finish(_("You must specify a directory to dump to"));
174 // see if we can access the directory the user wants us to use
175 if (! file_exists($directory)) {
176 if (! mkdir($directory, 0755))
177 $request->finish(fmt("Cannot create directory '%s'", $directory));
179 $html = HTML::p(fmt("Created directory '%s' for the page dump...",
182 $html = HTML::p(fmt("Using directory '%s'", $directory));
185 StartLoadDump($request, _("Dumping Pages"), $html);
187 $dbi = $request->getDbh();
188 $pages = $dbi->getAllPages();
190 while ($page = $pages->next()) {
191 if (! ini_get('safe_mode'))
192 set_time_limit(30); // Reset watchdog.
194 $filename = FilenameForPage($page->getName());
196 $msg = HTML(HTML::br(), $page->getName(), ' ... ');
198 if($page->getName() != $filename) {
199 $msg->pushContent(HTML::small(fmt("saved as %s", $filename)),
203 if ($request->getArg('include') == 'all')
204 $data = MailifyPage($page, 0);
206 $data = MailifyPage($page);
208 if ( !($fd = fopen("$directory/$filename", "w")) ) {
209 $msg->pushContent(HTML::strong(fmt("couldn't open file '%s' for writing",
210 "$directory/$filename")));
211 $request->finish($msg);
214 $num = fwrite($fd, $data, strlen($data));
215 $msg->pushContent(HTML::small(fmt("%s bytes written", $num)));
219 assert($num == strlen($data));
223 EndLoadDump($request);
227 function DumpHtmlToDir (&$request)
229 $directory = $request->getArg('directory');
230 if (empty($directory))
231 $request->finish(_("You must specify a directory to dump to"));
233 // see if we can access the directory the user wants us to use
234 if (! file_exists($directory)) {
235 if (! mkdir($directory, 0755))
236 $request->finish(fmt("Cannot create directory '%s'", $directory));
238 $html = HTML::p(fmt("Created directory '%s' for the page dump...",
241 $html = HTML::p(fmt("Using directory '%s'", $directory));
244 StartLoadDump($request, _("Dumping Pages"), $html);
246 $dbi = $request->getDbh();
247 $pages = $dbi->getAllPages();
249 global $HTML_DUMP_SUFFIX, $Theme;
250 if ($HTML_DUMP_SUFFIX)
251 $Theme->HTML_DUMP_SUFFIX = $HTML_DUMP_SUFFIX;
253 while ($page = $pages->next()) {
254 if (! ini_get('safe_mode'))
255 set_time_limit(30); // Reset watchdog.
257 $pagename = $page->getName();
258 $filename = FilenameForPage($pagename) . $Theme->HTML_DUMP_SUFFIX;
260 $msg = HTML(HTML::br(), $pagename, ' ... ');
262 if($page->getName() != $filename) {
263 $msg->pushContent(HTML::small(fmt("saved as %s", $filename)),
267 $revision = $page->getCurrentRevision();
269 require_once('lib/PageType.php');
270 $transformedContent = PageType($revision);
272 $template = new Template('browse', $request,
273 array('revision' => $revision,
274 'CONTENT' => $transformedContent));
276 $data = GeneratePageasXML($template, $pagename);
278 if ( !($fd = fopen("$directory/$filename", "w")) ) {
279 $msg->pushContent(HTML::strong(fmt("couldn't open file '%s' for writing",
280 "$directory/$filename")));
281 $request->finish($msg);
284 $num = fwrite($fd, $data, strlen($data));
285 $msg->pushContent(HTML::small(fmt("%s bytes written", $num) . "\n"));
289 assert($num == strlen($data));
293 //CopyImageFiles() will go here;
294 $Theme->$HTML_DUMP_SUFFIX = '';
296 EndLoadDump($request);
299 /* Known problem: any plugins or other code which echo()s text will
300 * lead to a corrupted html zip file which may produce the following
301 * errors upon unzipping:
303 * warning [wikihtml.zip]: 2401 extra bytes at beginning or within zipfile
304 * file #58: bad zipfile offset (local header sig): 177561
305 * (attempting to re-compensate)
307 * However, the actual wiki page data should be unaffected.
309 function MakeWikiZipHtml (&$request)
311 $zipname = "wikihtml.zip";
312 $zip = new ZipWriter("Created by PhpWiki " . PHPWIKI_VERSION, $zipname);
313 $dbi = $request->getDbh();
314 $pages = $dbi->getAllPages();
316 global $HTML_DUMP_SUFFIX, $Theme;
317 if ($HTML_DUMP_SUFFIX)
318 $Theme->HTML_DUMP_SUFFIX = $HTML_DUMP_SUFFIX;
320 while ($page = $pages->next()) {
321 if (! ini_get('safe_mode'))
322 set_time_limit(30); // Reset watchdog.
324 $current = $page->getCurrentRevision();
325 if ($current->getVersion() == 0)
328 $attrib = array('mtime' => $current->get('mtime'),
330 if ($page->get('locked'))
331 $attrib['write_protected'] = 1;
333 $pagename = $page->getName();
334 $filename = FilenameForPage($pagename) . $Theme->HTML_DUMP_SUFFIX;
335 $revision = $page->getCurrentRevision();
337 require_once('lib/PageType.php');
338 $transformedContent = PageType($revision);
340 $template = new Template('browse', $request,
341 array('revision' => $revision,
342 'CONTENT' => $transformedContent));
344 $data = GeneratePageasXML($template, $pagename);
346 $zip->addRegularFile( $filename, $data, $attrib);
348 // FIXME: Deal with images here.
350 $Theme->$HTML_DUMP_SUFFIX = '';
354 ////////////////////////////////////////////////////////////////
356 // Functions for restoring.
358 ////////////////////////////////////////////////////////////////
360 function SavePage (&$request, $pageinfo, $source, $filename)
362 $pagedata = $pageinfo['pagedata']; // Page level meta-data.
363 $versiondata = $pageinfo['versiondata']; // Revision level meta-data.
365 if (empty($pageinfo['pagename'])) {
366 PrintXML(HTML::dt(HTML::strong(_("Empty pagename!"))));
370 if (empty($versiondata['author_id']))
371 $versiondata['author_id'] = $versiondata['author'];
373 $pagename = $pageinfo['pagename'];
374 $content = $pageinfo['content'];
376 if ($pagename ==_("InterWikiMap"))
377 $content = _tryinsertInterWikiMap($content);
379 $dbi = $request->getDbh();
380 $page = $dbi->getPage($pagename);
382 $current = $page->getCurrentRevision();
383 // Try to merge if updated pgsrc contents are different. This
384 // whole thing is hackish
386 // TODO: try merge unless:
387 // if (current contents = default contents && pgsrc_version >=
388 // pgsrc_version) then just upgrade this pgsrc
389 $needs_merge = false;
393 if ($request->getArg('merge')) {
396 else if ($request->getArg('overwrite')) {
400 if ( (! $current->hasDefaultContents())
401 && ($current->getPackedContent() != $content)
402 && ($merging == true) ) {
403 require_once('lib/editpage.php');
404 $request->setArg('pagename', $pagename);
405 $r = $current->getVersion();
406 $request->setArg('revision', $current->getVersion());
407 $p = new LoadFileConflictPageEditor($request);
408 $p->_content = $content;
409 $p->_currentVersion = $r - 1;
410 $p->editPage($saveFailed = true);
411 return; //early return
414 foreach ($pagedata as $key => $value) {
416 $page->set($key, $value);
422 $mesg->pushContent(' ', fmt("from %s", $source));
425 $current = $page->getCurrentRevision();
426 if ($current->getVersion() == 0) {
427 $mesg->pushContent(' ', _("new page"));
431 if ( (! $current->hasDefaultContents())
432 && ($current->getPackedContent() != $content) ) {
434 $mesg->pushContent(' ',
435 fmt("has edit conflicts - overwriting anyway"));
437 if (substr_count($source, 'pgsrc')) {
438 $versiondata['author'] = _("The PhpWiki programming team");
439 // but leave authorid as userid who loaded the file
443 $mesg->pushContent(' ', fmt("has edit conflicts - skipped"));
444 $needs_merge = true; // hackish
448 else if ($current->getPackedContent() == $content
449 && $current->get('author') == $versiondata['author']) {
450 $mesg->pushContent(' ',
451 fmt("is identical to current version %d - skipped",
452 $current->getVersion()));
459 $new = $page->createRevision(WIKIDB_FORCE_CREATE, $content,
461 ExtractWikiPageLinks($content));
463 $mesg->pushContent(' ', fmt("- saved to database as version %d",
464 $new->getVersion()));
468 // hackish, $source contains needed path+filename
469 $f = str_replace(sprintf(_("MIME file %s"), ''), '', $f);
470 $f = str_replace(sprintf(_("Serialized file %s"), ''), '', $f);
471 $f = str_replace(sprintf(_("plain file %s"), ''), '', $f);
473 $meb = $Theme->makeButton($text = ("Merge Edit"),
474 $url = _("PhpWikiAdministration")
475 . "?action=loadfile&source=$f&merge=1",
476 $class = 'wikiadmin');
477 $owb = $Theme->makeButton($text = _("Restore Anyway"),
478 $url = _("PhpWikiAdministration")
479 . "?action=loadfile&source=$f&overwrite=1",
480 $class = 'wikiunsafe');
481 $mesg->pushContent(' ', $meb, " ", $owb);
485 PrintXML(HTML::dt(HTML::em(WikiLink($pagename))), $mesg);
487 PrintXML(HTML::dt(WikiLink($pagename)), $mesg);
491 function _tryinsertInterWikiMap($content) {
493 if (strpos($content, "<verbatim>")) {
494 //$error_html = " The newly loaded pgsrc already contains a verbatim block.";
497 if (!$goback && !defined('INTERWIKI_MAP_FILE')) {
498 $error_html = sprintf(" "._("%s: not defined"), "INTERWIKI_MAP_FILE");
501 if (!$goback && !file_exists(INTERWIKI_MAP_FILE)) {
502 $error_html = sprintf(" "._("%s: file not found"), INTERWIKI_MAP_FILE);
506 if (!empty($error_html))
507 trigger_error(_("Default InterWiki map file not loaded.")
508 . $error_html, E_USER_NOTICE);
513 $filename = INTERWIKI_MAP_FILE;
514 trigger_error(sprintf(_("Loading InterWikiMap from external file %s."),
515 $filename), E_USER_NOTICE);
517 $fd = fopen ($filename, "rb");
518 $data = fread ($fd, filesize($filename));
520 $content = $content . "\n<verbatim>\n$data</verbatim>\n";
524 function ParseSerializedPage($text, $default_pagename, $user)
526 if (!preg_match('/^a:\d+:{[si]:\d+/', $text))
529 $pagehash = unserialize($text);
531 // Split up pagehash into four parts:
534 // page-level meta-data
535 // revision-level meta-data
537 if (!defined('FLAG_PAGE_LOCKED'))
538 define('FLAG_PAGE_LOCKED', 1);
539 $pageinfo = array('pagedata' => array(),
540 'versiondata' => array());
542 $pagedata = &$pageinfo['pagedata'];
543 $versiondata = &$pageinfo['versiondata'];
546 if (empty($pagehash['pagename']))
547 $pagehash['pagename'] = $default_pagename;
548 if (empty($pagehash['author'])) {
549 $pagehash['author'] = $user->getId();
552 foreach ($pagehash as $key => $value) {
557 $pageinfo[$key] = $value;
560 $pageinfo[$key] = join("\n", $value);
563 if (($value & FLAG_PAGE_LOCKED) != 0)
564 $pagedata['locked'] = 'yes';
567 $pagedata[$key] = $value;
570 $versiondata['mtime'] = $value;
575 $versiondata[$key] = $value;
582 function SortByPageVersion ($a, $b) {
583 return $a['version'] - $b['version'];
586 function LoadFile (&$request, $filename, $text = false, $mtime = false)
588 if (!is_string($text)) {
590 $stat = stat($filename);
592 $text = implode("", file($filename));
595 if (! ini_get('safe_mode'))
596 set_time_limit(30); // Reset watchdog.
598 // FIXME: basename("filewithnoslashes") seems to return garbage sometimes.
599 $basename = basename("/dummy/" . $filename);
602 $mtime = time(); // Last resort.
604 $default_pagename = rawurldecode($basename);
606 if ( ($parts = ParseMimeifiedPages($text)) ) {
607 usort($parts, 'SortByPageVersion');
608 foreach ($parts as $pageinfo)
609 SavePage($request, $pageinfo, sprintf(_("MIME file %s"),
610 $filename), $basename);
612 else if ( ($pageinfo = ParseSerializedPage($text, $default_pagename,
613 $request->getUser())) ) {
614 SavePage($request, $pageinfo, sprintf(_("Serialized file %s"),
615 $filename), $basename);
618 $user = $request->getUser();
620 // Assume plain text file.
621 $pageinfo = array('pagename' => $default_pagename,
622 'pagedata' => array(),
624 => array('author' => $user->getId()),
625 'content' => preg_replace('/[ \t\r]*\n/', "\n",
628 SavePage($request, $pageinfo, sprintf(_("plain file %s"), $filename),
633 function LoadZip (&$request, $zipfile, $files = false, $exclude = false) {
634 $zip = new ZipReader($zipfile);
635 while (list ($fn, $data, $attrib) = $zip->readFile()) {
636 // FIXME: basename("filewithnoslashes") seems to return
637 // garbage sometimes.
638 $fn = basename("/dummy/" . $fn);
639 if ( ($files && !in_array($fn, $files))
640 || ($exclude && in_array($fn, $exclude)) ) {
641 PrintXML(HTML::dt(WikiLink($fn)),
642 HTML::dd(_("Skipping")));
646 LoadFile($request, $fn, $data, $attrib['mtime']);
650 function LoadDir (&$request, $dirname, $files = false, $exclude = false) {
651 $fileset = new LimitedFileSet($dirname, $files, $exclude);
653 if (($skiplist = $fileset->getSkippedFiles())) {
654 PrintXML(HTML::dt(HTML::strong(_("Skipping"))));
656 foreach ($skiplist as $file)
657 $list->pushContent(HTML::li(WikiLink($file)));
658 PrintXML(HTML::dd($list));
661 // Defer HomePage loading until the end. If anything goes wrong
662 // the pages can still be loaded again.
663 $files = $fileset->getFiles();
664 if (in_array(HOME_PAGE, $files)) {
665 $files = array_diff($files, array(HOME_PAGE));
666 $files[] = HOME_PAGE;
668 foreach ($files as $file)
669 LoadFile($request, "$dirname/$file");
672 class LimitedFileSet extends FileSet {
673 function LimitedFileSet($dirname, $_include, $exclude) {
674 $this->_includefiles = $_include;
675 $this->_exclude = $exclude;
676 $this->_skiplist = array();
677 parent::FileSet($dirname);
680 function _filenameSelector($fn) {
681 $incl = &$this->_includefiles;
682 $excl = &$this->_exclude;
684 if ( ($incl && !in_array($fn, $incl))
685 || ($excl && in_array($fn, $excl)) ) {
686 $this->_skiplist[] = $fn;
693 function getSkippedFiles () {
694 return $this->_skiplist;
699 function IsZipFile ($filename_or_fd)
701 // See if it looks like zip file
702 if (is_string($filename_or_fd))
704 $fd = fopen($filename_or_fd, "rb");
705 $magic = fread($fd, 4);
710 $fpos = ftell($filename_or_fd);
711 $magic = fread($filename_or_fd, 4);
712 fseek($filename_or_fd, $fpos);
715 return $magic == ZIP_LOCHEAD_MAGIC || $magic == ZIP_CENTHEAD_MAGIC;
719 function LoadAny (&$request, $file_or_dir, $files = false, $exclude = false)
721 // Try urlencoded filename for accented characters.
722 if (!file_exists($file_or_dir)) {
723 // Make sure there are slashes first to avoid confusing phps
724 // with broken dirname or basename functions.
725 // FIXME: windows uses \ and :
726 if (is_integer(strpos($file_or_dir, "/"))) {
727 $file_or_dir = FindFile($file_or_dir);
729 if (!file_exists($file_or_dir))
730 $file_or_dir = dirname($file_or_dir) . "/"
731 . urlencode(basename($file_or_dir));
733 // This is probably just a file.
734 $file_or_dir = urlencode($file_or_dir);
738 $type = filetype($file_or_dir);
739 if ($type == 'link') {
740 // For symbolic links, use stat() to determine
741 // the type of the underlying file.
742 list(,,$mode) = stat($file_or_dir);
743 $type = ($mode >> 12) & 017;
746 elseif ($type == 004)
751 $request->finish(fmt("Unable to load: %s", $file_or_dir));
753 else if ($type == 'dir') {
754 LoadDir($request, $file_or_dir, $files, $exclude);
756 else if ($type != 'file' && !preg_match('/^(http|ftp):/', $file_or_dir))
758 $request->finish(fmt("Bad file type: %s", $type));
760 else if (IsZipFile($file_or_dir)) {
761 LoadZip($request, $file_or_dir, $files, $exclude);
763 else /* if (!$files || in_array(basename($file_or_dir), $files)) */
765 LoadFile($request, $file_or_dir);
769 function LoadFileOrDir (&$request)
771 $source = $request->getArg('source');
772 StartLoadDump($request, sprintf(_("Loading '%s'"), $source));
774 LoadAny($request, $source);
776 EndLoadDump($request);
779 function SetupWiki (&$request)
781 global $GenericPages, $LANG;
784 //FIXME: This is a hack (err, "interim solution")
785 // This is a bogo-bogo-login: Login without
786 // saving login information in session state.
787 // This avoids logging in the unsuspecting
788 // visitor as "The PhpWiki programming team".
790 // This really needs to be cleaned up...
791 // (I'm working on it.)
792 $real_user = $request->_user;
793 $request->_user = new WikiUser(_("The PhpWiki programming team"),
796 StartLoadDump($request, _("Loading up virgin wiki"));
799 $pgsrc = FindLocalizedFile(WIKI_PGSRC);
800 $default_pgsrc = FindFile(DEFAULT_WIKI_PGSRC);
802 if ($default_pgsrc != $pgsrc)
803 LoadAny($request, $default_pgsrc, $GenericPages);
805 LoadAny($request, $pgsrc);
808 EndLoadDump($request);
811 function LoadPostFile (&$request)
813 $upload = $request->getUploadedFile('file');
816 $request->finish(_("No uploaded file to upload?")); // FIXME: more concise message
819 // Dump http headers.
820 StartLoadDump($request, sprintf(_("Uploading %s"), $upload->getName()));
823 $fd = $upload->open();
825 LoadZip($request, $fd, false, array(_("RecentChanges")));
827 LoadFile($request, $upload->getName(), $upload->getContents());
830 EndLoadDump($request);
834 $Log: not supported by cvs2svn $
835 Revision 1.73 2003/01/28 21:09:17 zorloc
836 The get_cfg_var() function should only be used when one is
837 interested in the value from php.ini or similar. Use ini_get()
838 instead to get the effective value of a configuration variable.
841 Revision 1.72 2003/01/03 22:25:53 carstenklapp
842 Cosmetic fix to "Merge Edit" & "Overwrite" buttons. Added "The PhpWiki
843 programming team" as author when loading from pgsrc. Source
846 Revision 1.71 2003/01/03 02:48:05 carstenklapp
847 function SavePage: Added loadfile options for overwriting or merge &
848 compare a loaded pgsrc file with an existing page.
850 function LoadAny: Added a general error message when unable to load a
851 file instead of defaulting to "Bad file type".
860 // c-hanging-comment-ender-p: nil
861 // indent-tabs-mode: nil