4 * PhpWikiPlugin for PhpWiki developers to generate single page dumps
5 * for checking into cvs, or for users or the admin to produce a
6 * downloadable page dump of a single page.
8 * This plugin will also be useful to (semi-)automatically sync pages
9 * directly between two wikis. First the LoadFile function of
10 * PhpWikiAdministration needs to be updated to handle URLs again, and
11 * add loading capability from InterWiki addresses.
13 * Multiple revisions in one file handled by format=backup
15 * TODO: What about comments/summary field? quoted-printable?
19 * http://...phpwiki/PageDump?page=HomePage?format=forcvs
20 * http://...phpwiki/index.php?PageDump&page=HomePage
21 * http://...phpwiki/index.php?PageDump&page=HomePage&download=1
23 * <?plugin PageDump page=HomePage?>
24 * Dynamic form (put both on the page):
26 * <?plugin-form PageDump?>
27 * Typical usage: as actionbar button
30 class WikiPlugin_PageDump
38 function getDescription() {
39 return _("View a single page dump online.");
42 function getVersion() {
43 return preg_replace("/[Revision: $]/", '',
47 function getDefaultArguments() {
48 return array('s' => false,
49 'page' => '[pagename]',
50 //'encoding' => 'binary', // 'binary', 'quoted-printable'
51 'format' => false, // 'normal', 'forcvs', 'backup'
52 // display within WikiPage or give a downloadable
57 function run($dbi, $argstr, &$request, $basepage) {
58 extract($this->getArgs($argstr, $request));
64 if (! $dbi->isWikiPage($page) )
65 return fmt("Page %s not found.",
66 WikiLink($page, 'unknown'));
68 // Check if user is allowed to get the Page.
69 if (!mayAccessPage ('view', $page)) {
70 return $this->error(sprintf(_("Illegal access to page %s: no read access"),
74 $p = $dbi->getPage($page);
75 include_once("lib/loadsave.php");
76 $mailified = MailifyPage($p, ($format == 'backup') ? 99 : 1);
78 // fixup_headers massages the page dump headers depending on
79 // the 'format' argument, 'normal'(default) or 'forcvs'.
81 // Normal: Don't add X-Rcs-Id, add unique Message-Id, don't
82 // strip any fields from Content-Type.
84 // ForCVS: Add empty X-Rcs-Id, strip attributes from
85 // Content-Type field: "author", "version", "lastmodified",
86 // "author_id", "hits".
88 $this->pagename = $page;
89 $this->generateMessageId($mailified);
90 if ($format == 'forcvs')
91 $this->fixup_headers_forcvs($mailified);
92 else // backup or normal
93 $this->fixup_headers($mailified);
96 // TODO: we need a way to hook into the generated headers, to override
97 // Content-Type, Set-Cookie, Cache-control, ...
98 $request->discardOutput(); // Hijack the http request from PhpWiki.
99 ob_end_clean(); // clean up after hijacking $request
100 //ob_end_flush(); //debugging
101 $filename = FilenameForPage($page);
102 Header("Content-disposition: attachment; filename=\""
104 // Read charset from generated page itself.
105 // Inconsequential at the moment, since loadsave.php
106 // always generates headers.
107 $charset = $p->get('charset');
108 if (!$charset) $charset = $GLOBALS['charset'];
109 // We generate 3 Content-Type headers! first in loadsave,
110 // then here and the mimified string $mailified also has it!
111 // This one is correct and overwrites the others.
112 Header("Content-Type: application/octet-stream; name=\""
113 . $filename . "\"; charset=\"" . $charset
115 $request->checkValidators();
116 // let $request provide last modified & etag
117 Header("Content-Id: <" . $this->MessageId . ">");
118 // be nice to http keepalive~s
119 Header("Content-Length: " . strlen($mailified));
121 // Here comes our prepared mime file
123 exit; // noreturn! php exits.
126 // We are displaing inline preview in a WikiPage, so wrap the
127 // text if it is too long--unless quoted-printable (TODO).
128 $mailified = safe_wordwrap($mailified, 70);
130 $dlcvs = Button(array(//'page' => $page,
131 'action' => $this->getName(),
134 _("Download for CVS"),
136 $dl = Button(array(//'page' => $page,
137 'action' => $this->getName(),
139 _("Download for backup"),
141 $dlall = Button(array(//'page' => $page,
142 'action' => $this->getName(),
145 _("Download all revisions for backup"),
148 $h2 = HTML::h2(fmt("Preview: Page dump of %s",
149 WikiLink($page, 'auto')));
151 if (!$Sep = $WikiTheme->getButtonSeparator())
154 if ($format == 'forcvs') {
155 $desc = _("(formatted for PhpWiki developers as pgsrc template, not for backing up)");
156 $altpreviewbuttons = HTML(
157 Button(array('action' => $this->getName()),
158 _("Preview as normal format"),
162 'action' => $this->getName(),
163 'format'=> 'backup'),
164 _("Preview as backup format"),
167 elseif ($format == 'backup') {
168 $desc = _("(formatted for backing up: all revisions)"); // all revisions
169 $altpreviewbuttons = HTML(
170 Button(array('action' => $this->getName(),
171 'format'=> 'forcvs'),
172 _("Preview as developer format"),
176 'action' => $this->getName(),
178 _("Preview as normal format"),
181 $desc = _("(normal formatting: latest revision only)");
182 $altpreviewbuttons = HTML(
183 Button(array('action' => $this->getName(),
184 'format'=> 'forcvs'),
185 _("Preview as developer format"),
189 'action' => $this->getName(),
190 'format'=> 'backup'),
191 _("Preview as backup format"),
195 _("Please use one of the downloadable versions rather than copying and pasting from the above preview.")
197 _("The wordwrap of the preview doesn't take nested markup or list indentation into consideration!")
200 _("PhpWiki developers should manually inspect the downloaded file for nested markup before rewrapping with emacs and checking into CVS.")
204 return HTML($h2, HTML::em($desc),
205 HTML::pre($mailified),
207 HTML::div(array('class' => 'errors'),
208 HTML::strong(_("Warning:")),
210 $dl, $Sep, $dlall, $Sep, $dlcvs
214 // function handle_plugin_args_cruft(&$argstr, &$args) {
217 function generateMessageId($mailified) {
218 $array = explode("\n", $mailified);
219 // Extract lastmodifed from mailified document for Content-Id
220 // and/or Message-Id header, NOT from DB (page could have been
221 // edited by someone else since we started).
222 $m1 = preg_grep("/^\s+lastmodified\=(.*);/", $array);
223 $m1 = array_values($m1); //reset resulting keys
225 $m2 = preg_split("/(^\s+lastmodified\=)|(;)/", $m1[0], 2,
226 PREG_SPLIT_NO_EMPTY);
228 // insert message id into actual message when appropriate, NOT
229 // into http header should be part of fixup_headers, in the
231 // <abbrphpwikiversion.mtimeepochTZ%InterWikiLinktothispage@hostname>
232 // Hopefully this provides a unique enough identifier without
233 // using md5. Even though this particular wiki may not
234 // actually be part of InterWiki, including this info provides
235 // the wiki name and name of the page which is being
236 // represented as a text message.
237 $this->MessageId = implode('', explode('.', PHPWIKI_VERSION))
238 . "-" . $m2[0] . date("O")
239 //. "-". rawurlencode(WIKI_NAME.":" . $request->getURLtoSelf())
240 . "-". rawurlencode(WIKI_NAME.":" . $this->pagename)
241 . "@". rawurlencode(SERVER_NAME);
244 function fixup_headers(&$mailified) {
245 $return = explode("\n", $mailified);
247 // Leave message intact for backing up, just add Message-Id header before transmitting.
248 $item_to_insert = "Message-Id: <" . $this->MessageId .">";
249 $insert_into_key_position = 2;
250 $returnval_ignored = array_splice($return,
251 $insert_into_key_position,
254 $mailified = implode("\n", array_values($return));
257 function fixup_headers_forcvs(&$mailified) {
258 $array = explode("\n", $mailified);
260 // Massage headers to prepare for developer checkin to CVS.
261 $item_to_insert = "X-Rcs-Id: \$Id\$";
262 $insert_into_key_position = 2;
263 $returnval_ignored = array_splice($array,
264 $insert_into_key_position,
267 $item_to_insert = " pgsrc_version=\"2 \$Revision\$\";";
268 $insert_into_key_position = 5;
269 $returnval_ignored = array_splice($array,
270 $insert_into_key_position,
273 Strip out all this junk:
276 lastmodified=1041561552;
280 $killme = array("author", "version", "lastmodified",
281 "author_id", "hits", "owner", "acl");
282 // UltraNasty, fixme:
283 foreach ($killme as $pattern) {
284 $array = preg_replace("/^\s\s$pattern\=.*;/",
285 /*$replacement =*/"zzzjunk", $array);
287 // remove deleted values from array
288 for ($i = 0; $i < count($array); $i++ ) {
289 if(trim($array[$i]) != "zzzjunk") { //nasty, fixme
290 //trigger_error("'$array[$i]'");//debugging
291 $return[] = $array[$i];
295 $mailified = implode("\n", $return);
304 // c-hanging-comment-ender-p: nil
305 // indent-tabs-mode: nil