4 * Copyright (C) 2003 $ThePhpWikiProgrammingTeam
6 * This file is part of PhpWiki.
8 * PhpWiki is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * PhpWiki is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with PhpWiki; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 * PhpWikiPlugin for PhpWiki developers to generate single page dumps
25 * for checking into Subversion, or for users or the admin to produce a
26 * downloadable page dump of a single page.
28 * This plugin will also be useful to (semi-)automatically sync pages
29 * directly between two wikis. First the LoadFile function of
30 * PhpWikiAdministration needs to be updated to handle URLs again, and
31 * add loading capability from InterWiki addresses.
33 * Multiple revisions in one file handled by format=backup
35 * TODO: What about comments/summary field? quoted-printable?
39 * http://...phpwiki/PageDump?page=HomePage?format=forsvn
40 * http://...phpwiki/index.php?PageDump&page=HomePage
41 * http://...phpwiki/index.php?PageDump&page=HomePage&download=1
43 * <<PageDump page=HomePage>>
44 * Dynamic form (put both on the page):
46 * <?plugin-form PageDump?>
47 * Typical usage: as actionbar button
50 class WikiPlugin_PageDump
58 function getDescription() {
59 return _("View a single page dump online.");
62 function getDefaultArguments() {
63 return array('s' => false,
64 'page' => '[pagename]',
65 //'encoding' => 'binary', // 'binary', 'quoted-printable'
66 'format' => false, // 'normal', 'forsvn', 'forcvs', 'backup'
67 // display within WikiPage or give a downloadable
72 function run($dbi, $argstr, &$request, $basepage) {
73 extract($this->getArgs($argstr, $request));
79 if (! $dbi->isWikiPage($page) )
80 return fmt("Page %s not found.",
81 WikiLink($page, 'unknown'));
83 // Check if user is allowed to get the Page.
84 if (!mayAccessPage ('view', $page)) {
85 return $this->error(sprintf(_("Illegal access to page %s: no read access"),
89 $p = $dbi->getPage($page);
90 include_once("lib/loadsave.php");
91 $mailified = MailifyPage($p, ($format == 'backup') ? 99 : 1);
93 // fixup_headers massages the page dump headers depending on
94 // the 'format' argument, 'normal'(default) or 'forsvn'.
96 // Normal: Don't add X-Rcs-Id, add unique Message-Id, don't
97 // strip any fields from Content-Type.
99 // ForCVS: Add empty X-Rcs-Id, strip attributes from
100 // Content-Type field: "author", "version", "lastmodified",
101 // "author_id", "hits".
103 $this->pagename = $page;
104 $this->generateMessageId($mailified);
105 if (($format == 'forsvn') || ($format == 'forcvs'))
106 $this->fixup_headers_forsvn($mailified);
107 else // backup or normal
108 $this->fixup_headers($mailified);
111 // TODO: we need a way to hook into the generated headers, to override
112 // Content-Type, Set-Cookie, Cache-control, ...
113 $request->discardOutput(); // Hijack the http request from PhpWiki.
114 ob_end_clean(); // clean up after hijacking $request
115 //while (@ob_end_flush()); //debugging
116 $filename = FilenameForPage($page);
117 Header("Content-disposition: attachment; filename=\""
119 // Read charset from generated page itself.
120 // Inconsequential at the moment, since loadsave.php
121 // always generates headers.
122 $charset = $p->get('charset');
123 if (!$charset) $charset = $GLOBALS['charset'];
124 // We generate 3 Content-Type headers! first in loadsave,
125 // then here and the mimified string $mailified also has it!
126 // This one is correct and overwrites the others.
127 Header("Content-Type: application/octet-stream; name=\""
128 . $filename . "\"; charset=\"" . $charset
130 $request->checkValidators();
131 // let $request provide last modified & etag
132 Header("Content-Id: <" . $this->MessageId . ">");
133 // be nice to http keepalive~s
134 Header("Content-Length: " . strlen($mailified));
136 // Here comes our prepared mime file
138 exit; // noreturn! php exits.
141 // We are displaing inline preview in a WikiPage, so wrap the
142 // text if it is too long--unless quoted-printable (TODO).
143 $mailified = wordwrap($mailified, 70);
145 $dlsvn = Button(array(//'page' => $page,
146 'action' => $this->getName(),
149 _("Download for Subversion"),
151 $dl = Button(array(//'page' => $page,
152 'action' => $this->getName(),
154 _("Download for backup"),
156 $dlall = Button(array(//'page' => $page,
157 'action' => $this->getName(),
160 _("Download all revisions for backup"),
163 $h2 = HTML::h2(fmt("Preview: Page dump of %s",
164 WikiLink($page, 'auto')));
166 if (!$Sep = $WikiTheme->getButtonSeparator())
169 if ($format == 'forsvn') {
170 $desc = _("(formatted for PhpWiki developers as pgsrc template, not for backing up)");
171 $altpreviewbuttons = HTML(
172 Button(array('action' => $this->getName()),
173 _("Preview as normal format"),
177 'action' => $this->getName(),
178 'format'=> 'backup'),
179 _("Preview as backup format"),
182 elseif ($format == 'backup') {
183 $desc = _("(formatted for backing up: all revisions)"); // all revisions
184 $altpreviewbuttons = HTML(
185 Button(array('action' => $this->getName(),
186 'format'=> 'forsvn'),
187 _("Preview as developer format"),
191 'action' => $this->getName(),
193 _("Preview as normal format"),
196 $desc = _("(normal formatting: latest revision only)");
197 $altpreviewbuttons = HTML(
198 Button(array('action' => $this->getName(),
199 'format'=> 'forsvn'),
200 _("Preview as developer format"),
204 'action' => $this->getName(),
205 'format'=> 'backup'),
206 _("Preview as backup format"),
210 _("Please use one of the downloadable versions rather than copying and pasting from the above preview.")
212 _("The wordwrap of the preview doesn't take nested markup or list indentation into consideration!")
215 _("PhpWiki developers should manually inspect the downloaded file for nested markup before rewrapping with emacs and checking into Subversion.")
219 return HTML($h2, HTML::em($desc),
220 HTML::pre($mailified),
222 HTML::div(array('class' => 'error'),
223 HTML::strong(_("Warning:")),
225 $dl, $Sep, $dlall, $Sep, $dlsvn
229 // function handle_plugin_args_cruft(&$argstr, &$args) {
232 function generateMessageId($mailified) {
233 $array = explode("\n", $mailified);
234 // Extract lastmodifed from mailified document for Content-Id
235 // and/or Message-Id header, NOT from DB (page could have been
236 // edited by someone else since we started).
237 $m1 = preg_grep("/^\s+lastmodified\=(.*);/", $array);
238 $m1 = array_values($m1); //reset resulting keys
240 $m2 = preg_split("/(^\s+lastmodified\=)|(;)/", $m1[0], 2,
241 PREG_SPLIT_NO_EMPTY);
243 // insert message id into actual message when appropriate, NOT
244 // into http header should be part of fixup_headers, in the
246 // <abbrphpwikiversion.mtimeepochTZ%InterWikiLinktothispage@hostname>
247 // Hopefully this provides a unique enough identifier without
248 // using md5. Even though this particular wiki may not
249 // actually be part of InterWiki, including this info provides
250 // the wiki name and name of the page which is being
251 // represented as a text message.
252 $this->MessageId = implode('', explode('.', PHPWIKI_VERSION))
253 . "-" . $m2[0] . date("O")
254 //. "-". rawurlencode(WIKI_NAME.":" . $request->getURLtoSelf())
255 . "-". rawurlencode(WIKI_NAME.":" . $this->pagename)
256 . "@". rawurlencode(SERVER_NAME);
259 function fixup_headers(&$mailified) {
260 $return = explode("\n", $mailified);
262 // Leave message intact for backing up, just add Message-Id header before transmitting.
263 $item_to_insert = "Message-Id: <" . $this->MessageId .">";
264 $insert_into_key_position = 2;
265 $returnval_ignored = array_splice($return,
266 $insert_into_key_position,
269 $mailified = implode("\n", array_values($return));
272 function fixup_headers_forsvn(&$mailified) {
273 $array = explode("\n", $mailified);
275 // Massage headers to prepare for developer checkin to Subversion.
276 $item_to_insert = "X-Rcs-Id: \$Id\$";
277 $insert_into_key_position = 2;
278 $returnval_ignored = array_splice($array,
279 $insert_into_key_position,
282 $item_to_insert = " pgsrc_version=\"2 \$Revision\$\";";
283 $insert_into_key_position = 5;
284 $returnval_ignored = array_splice($array,
285 $insert_into_key_position,
288 Strip out all this junk:
291 lastmodified=1041561552;
295 $killme = array("author", "version", "lastmodified",
296 "author_id", "hits", "owner", "acl");
297 // UltraNasty, fixme:
298 foreach ($killme as $pattern) {
299 $array = preg_replace("/^\s\s$pattern\=.*;/",
300 /*$replacement =*/"zzzjunk", $array);
302 // remove deleted values from array
303 for ($i = 0; $i < count($array); $i++ ) {
304 if(trim($array[$i]) != "zzzjunk") { //nasty, fixme
305 //trigger_error("'$array[$i]'");//debugging
306 $return[] = $array[$i];
310 $mailified = implode("\n", $return);
318 // c-hanging-comment-ender-p: nil
319 // indent-tabs-mode: nil