3 * Copyright © 2003 $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 along
18 * with PhpWiki; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 * SPDX-License-Identifier: GPL-2.0-or-later
26 * PhpWikiPlugin for PhpWiki developers to generate single page dumps
27 * for checking into Subversion, or for users or the admin to produce a
28 * downloadable page dump of a single page.
30 * This plugin will also be useful to (semi-)automatically sync pages
31 * directly between two wikis. First the LoadFile function of
32 * PhpWikiAdministration needs to be updated to handle URLs again, and
33 * add loading capability from InterWiki addresses.
35 * Multiple revisions in one file handled by format=backup
37 * TODO: What about comments/summary field? quoted-printable?
41 * http://...phpwiki/PageDump?page=HomePage?format=forsvn
42 * http://...phpwiki/index.php?PageDump&page=HomePage
43 * http://...phpwiki/index.php?PageDump&page=HomePage&download=1
45 * <<PageDump page=HomePage>>
46 * Dynamic form (put both on the page):
48 * <?plugin-form PageDump?>
49 * Typical usage: as actionbar button
52 class WikiPlugin_PageDump
58 function getDescription()
60 return _("View a single page dump online.");
63 function getDefaultArguments()
65 return array('s' => false,
66 'page' => '[pagename]',
67 //'encoding' => 'binary', // 'binary', 'quoted-printable'
68 'format' => false, // 'normal', 'forsvn', 'backup'
69 // display within WikiPage or give a downloadable
74 function run($dbi, $argstr, &$request, $basepage)
76 extract($this->getArgs($argstr, $request));
84 if (!$dbi->isWikiPage($page)) {
85 return $this->error(sprintf(_("Page “%s” does not exist."), $page));
88 // Check if user is allowed to get the Page.
89 if (!mayAccessPage('view', $page)) {
90 return $this->error(sprintf(_("Illegal access to page %s: no read access"),
94 $p = $dbi->getPage($page);
95 include_once 'lib/loadsave.php';
96 $mailified = MailifyPage($p, ($format == 'backup') ? 99 : 1);
98 // fixup_headers massages the page dump headers depending on
99 // the 'format' argument, 'normal'(default) or 'forsvn'.
101 // Normal: add unique Message-Id, don't
102 // strip any fields from Content-Type.
104 $this->pagename = $page;
105 $this->generateMessageId($mailified);
106 if ($format == 'forsvn')
107 $this->fixup_headers_forsvn($mailified);
108 else // backup or normal
109 $this->fixup_headers($mailified);
112 // TODO: we need a way to hook into the generated headers, to override
113 // Content-Type, Set-Cookie, Cache-control, ...
114 $request->discardOutput(); // Hijack the http request from PhpWiki.
115 ob_end_clean(); // clean up after hijacking $request
116 //while (@ob_end_flush()); //debugging
117 $filename = FilenameForPage($page);
118 header("Content-disposition: attachment; filename=\""
120 // We generate 3 Content-Type headers! first in loadsave,
121 // then here and the mimified string $mailified also has it!
122 // This one is correct and overwrites the others.
123 header("Content-Type: application/octet-stream; name=\""
124 . $filename . "\"; charset=\"" . 'UTF-8'
126 $request->checkValidators();
127 // let $request provide last modified & etag
128 header("Content-Id: <" . $this->MessageId . ">");
129 // be nice to http keepalive~s
130 header("Content-Length: " . strlen($mailified));
132 // Here comes our prepared mime file
134 exit(); // noreturn! php exits.
136 // We are displaing inline preview in a WikiPage, so wrap the
137 // text if it is too long--unless quoted-printable (TODO).
138 $mailified = wordwrap($mailified, 70);
140 $dlsvn = Button(array( //'page' => $page,
141 'action' => $this->getName(),
142 'format' => 'forsvn',
144 _("Download for Subversion"),
146 $dl = Button(array( //'page' => $page,
147 'action' => $this->getName(),
149 _("Download for backup"),
151 $dlall = Button(array( //'page' => $page,
152 'action' => $this->getName(),
153 'format' => 'backup',
155 _("Download all revisions for backup"),
158 $h2 = HTML::h2(fmt("Preview: Page dump of %s",
159 WikiLink($page, 'auto')));
161 if (!$Sep = $WikiTheme->getButtonSeparator())
164 if ($format == 'forsvn') {
165 $desc = _("(formatted for PhpWiki developers as pgsrc template, not for backing up)");
166 $altpreviewbuttons = HTML(
167 Button(array('action' => $this->getName()),
168 _("Preview as normal format"),
172 'action' => $this->getName(),
173 'format' => 'backup'),
174 _("Preview as backup format"),
176 } elseif ($format == 'backup') {
177 $desc = _("(formatted for backing up: all revisions)"); // all revisions
178 $altpreviewbuttons = HTML(
179 Button(array('action' => $this->getName(),
180 'format' => 'forsvn'),
181 _("Preview as developer format"),
185 'action' => $this->getName(),
187 _("Preview as normal format"),
190 $desc = _("(normal formatting: latest revision only)");
191 $altpreviewbuttons = HTML(
192 Button(array('action' => $this->getName(),
193 'format' => 'forsvn'),
194 _("Preview as developer format"),
198 'action' => $this->getName(),
199 'format' => 'backup'),
200 _("Preview as backup format"),
204 _("Please use one of the downloadable versions rather than copying and pasting from the above preview.")
206 _("The wordwrap of the preview doesn't take nested markup or list indentation into consideration!")
209 _("PhpWiki developers should manually inspect the downloaded file for nested markup before rewrapping with emacs and checking into Subversion.")
213 return HTML($h2, HTML::em($desc),
214 HTML::pre($mailified),
216 HTML::div(array('class' => 'error'),
217 HTML::strong(_("Warning:")),
219 $dl, $Sep, $dlall, $Sep, $dlsvn
223 function generateMessageId($mailified)
225 $array = explode("\n", $mailified);
226 // Extract lastmodifed from mailified document for Content-Id
227 // and/or Message-Id header, NOT from DB (page could have been
228 // edited by someone else since we started).
229 $m1 = preg_grep("/^\s+lastmodified\=(.*);/", $array);
230 $m1 = array_values($m1); //reset resulting keys
232 $m2 = preg_split("/(^\s+lastmodified\=)|(;)/", $m1[0], 2,
233 PREG_SPLIT_NO_EMPTY);
235 // insert message id into actual message when appropriate, NOT
236 // into http header should be part of fixup_headers, in the
238 // <abbrphpwikiversion.mtimeepochTZ%InterWikiLinktothispage@hostname>
239 // Hopefully this provides a unique enough identifier without
240 // using md5. Even though this particular wiki may not
241 // actually be part of InterWiki, including this info provides
242 // the wiki name and name of the page which is being
243 // represented as a text message.
244 $this->MessageId = implode('', explode('.', PHPWIKI_VERSION))
245 . "-" . $m2[0] . date("O")
246 //. "-". rawurlencode(WIKI_NAME.":" . $request->getURLtoSelf())
247 . "-" . rawurlencode(WIKI_NAME . ":" . $this->pagename)
248 . "@" . rawurlencode(SERVER_NAME);
251 function fixup_headers(&$mailified)
253 $return = explode("\n", $mailified);
255 // Leave message intact for backing up, just add Message-Id header before transmitting.
256 $item_to_insert = "Message-Id: <" . $this->MessageId . ">";
257 $insert_into_key_position = 2;
258 $returnval_ignored = array_splice($return,
259 $insert_into_key_position,
262 $mailified = implode("\n", array_values($return));
265 function fixup_headers_forsvn(&$mailified)
267 $array = explode("\n", $mailified);
269 // Massage headers to prepare for developer checkin to Subversion.
271 Strip out all this junk:
274 lastmodified=1041561552;
278 $killme = array("author", "version", "lastmodified",
279 "author_id", "hits", "owner", "acl");
280 // UltraNasty, fixme:
281 foreach ($killme as $pattern) {
282 $array = preg_replace("/^\s\s$pattern\=.*;/",
286 // remove deleted values from array
287 for ($i = 0; $i < count($array); $i++) {
288 if (trim($array[$i]) != "zzzjunk") { //nasty, fixme
289 $return[] = $array[$i];
293 $mailified = implode("\n", $return);