]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/PageType.php
better experimental PDF support
[SourceForge/phpwiki.git] / lib / PageType.php
1 <?php // -*-php-*-
2 rcs_id('$Id: PageType.php,v 1.24 2004-03-26 00:22:37 rurban Exp $');
3 /*
4  Copyright 1999,2000,2001,2002,2003,2004 $ThePhpWikiProgrammingTeam
5
6  This file is part of PhpWiki.
7
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.
12
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.
17
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
21  */
22
23 require_once('lib/CachedMarkup.php');
24
25 /** A cacheable formatted wiki page.
26  */
27 class TransformedText extends CacheableMarkup {
28     /** Constructor.
29      *
30      * @param WikiDB_Page $page
31      * @param string $text  The packed page revision content.
32      * @param hash $meta    The version meta-data.
33      * @param string $type_override  For markup of page using a different
34      *        pagetype than that specified in its version meta-data.
35      */
36     function TransformedText($page, $text, $meta, $type_override=false) {
37         @$pagetype = $meta['pagetype'];
38         if ($type_override)
39             $pagetype = $type_override;
40         $this->_type = PageType::GetPageType($pagetype);
41         $this->CacheableMarkup($this->_type->transform($page, $text, $meta),
42                                $page->getName());
43     }
44
45     function getType() {
46         return $this->_type;
47     }
48 }
49
50 /**
51  * A page type descriptor.
52  *
53  * Encapsulate information about page types.
54  *
55  * Currently the only information encapsulated is how to format
56  * the specific page type.  In the future or capabilities may be
57  * added, e.g. the abilities to edit different page types (differently.)
58  * e.g. Support for the javascript htmlarea editor, which can only edit 
59  * pure HTML.
60  *
61  * IMPORTANT NOTE: Since the whole PageType class gets stored (serialized)
62  * as of the cached marked-up page, it is important that the PageType classes
63  * not have large amounts of class data.  (No class data is even better.)
64  */
65 class PageType {
66     /**
67      * Get a page type descriptor.
68      *
69      * This is a static member function.
70      *
71      * @param string $pagetype  Name of the page type.
72      * @return PageType  An object which is a subclass of PageType.
73      */
74     function GetPageType ($name=false) {
75         if (!$name)
76             $name = 'wikitext';
77         if ($name) {
78             $class = "PageType_" . (string)$name;
79             if (class_exists($class))
80                 return new $class;
81             trigger_error(sprintf("PageType '%s' unknown", (string)$name),
82                           E_USER_WARNING);
83         }
84         return new PageType_wikitext;
85     }
86
87     /**
88      * Get the name of this page type.
89      *
90      * @return string  Page type name.
91      */
92     function getName() {
93         if (!preg_match('/^PageType_(.+)$/i', get_class($this), $m))
94             trigger_error("Bad class name for formatter(?)", E_USER_ERROR);
95         return $m[1];
96     }
97
98     /**
99      * Transform page text.
100      *
101      * @param WikiDB_Page $page
102      * @param string $text
103      * @param hash $meta Version meta-data
104      * @return XmlContent The transformed page text.
105      */
106     function transform($page, $text, $meta) {
107         $fmt_class = 'PageFormatter_' . $this->getName();
108         $formatter = new $fmt_class($page, $meta);
109         return $formatter->format($text);
110     }
111 }
112
113 class PageType_wikitext extends PageType {}
114 class PageType_html extends PageType {}
115 class PageType_pdf extends PageType {}
116
117 class PageType_wikiblog extends PageType {}
118 class PageType_comment extends PageType {}
119 class PageType_wikiforum extends PageType {}
120
121 class PageType_interwikimap extends PageType
122 {
123     function PageType_interwikimap() {
124         global $request;
125         $dbi = $request->getDbh();
126         $intermap = $this->_getMapFromWikiPage($dbi->getPage(_("InterWikiMap")));
127         if (!$intermap && defined('INTERWIKI_MAP_FILE'))
128             $intermap = $this->_getMapFromFile(INTERWIKI_MAP_FILE);
129
130         $this->_map = $this->_parseMap($intermap);
131         $this->_regexp = $this->_getRegexp();
132     }
133
134     function GetMap ($request = false) {
135         if (empty($this->_map)) {
136             $map = new PageType_interwikimap();
137             return $map;
138         } else {
139             return $this;
140         }
141     }
142
143     function getRegexp() {
144         return $this->_regexp;
145     }
146
147     function link ($link, $linktext = false) {
148
149         list ($moniker, $page) = split (":", $link, 2);
150         
151         if (!isset($this->_map[$moniker])) {
152             return HTML::span(array('class' => 'bad-interwiki'),
153                               $linktext ? $linktext : $link);
154         }
155
156         $url = $this->_map[$moniker];
157         
158         // Urlencode page only if it's a query arg.
159         // FIXME: this is a somewhat broken heuristic.
160         $page_enc = strstr($url, '?') ? rawurlencode($page) : $page;
161
162         if (strstr($url, '%s'))
163             $url = sprintf($url, $page_enc);
164         else
165             $url .= $page_enc;
166
167         $link = HTML::a(array('href' => $url));
168
169         if (!$linktext) {
170             $link->pushContent(PossiblyGlueIconToText('interwiki', "$moniker:"),
171                                HTML::span(array('class' => 'wikipage'), $page));
172             $link->setAttr('class', 'interwiki');
173         }
174         else {
175             $link->pushContent(PossiblyGlueIconToText('interwiki', $linktext));
176             $link->setAttr('class', 'named-interwiki');
177         }
178         
179         return $link;
180     }
181
182
183     function _parseMap ($text) {
184         global $AllowedProtocols;
185         if (!preg_match_all("/^\s*(\S+)\s+(\S+)/m",
186                             $text, $matches, PREG_SET_ORDER))
187             return false;
188         foreach ($matches as $m) {
189             $map[$m[1]] = $m[2];
190         }
191         if (empty($map['Upload']))
192             $map['Upload'] = SERVER_URL . ((substr(DATA_PATH,0,1)=='/') ? '' : "/") . DATA_PATH . '/uploads/';
193         return $map;
194     }
195
196     function _getMapFromWikiPage ($page) {
197         if (! $page->get('locked'))
198             return false;
199         
200         $current = $page->getCurrentRevision();
201         
202         if (preg_match('|^<verbatim>\n(.*)^</verbatim>|ms',
203                        $current->getPackedContent(), $m)) {
204             return $m[1];
205         }
206         return false;
207     }
208
209     // Fixme!
210     function _getMapFromFile ($filename) {
211         if (defined('WARN_NONPUBLIC_INTERWIKIMAP') and WARN_NONPUBLIC_INTERWIKIMAP) {
212             $error_html = sprintf(_("Loading InterWikiMap from external file %s."), $filename);
213             trigger_error( $error_html, E_USER_NOTICE );
214         }
215         if (!file_exists($filename)) {
216             $finder = new FileFinder();
217             $filename = $finder->findFile(INTERWIKI_MAP_FILE);
218         }
219         @$fd = fopen ($filename, "rb");
220         @$data = fread ($fd, filesize($filename));
221         @fclose ($fd);
222
223         return $data;
224     }
225
226     function _getRegexp () {
227         if (!$this->_map)
228             return '(?:(?!a)a)'; //  Never matches.
229         
230         foreach (array_keys($this->_map) as $moniker)
231             $qkeys[] = preg_quote($moniker, '/');
232         return "(?:" . join("|", $qkeys) . ")";
233     }
234 }
235
236
237 /** How to transform text.
238  */
239 class PageFormatter {
240     /** Constructor.
241      *
242      * @param WikiDB_Page $page
243      * @param hash $meta Version meta-data.
244      */
245     function PageFormatter($page, $meta) {
246         $this->_page = $page;
247         $this->_meta = $meta;
248         if (!empty($meta['markup']))
249             $this->_markup = $meta['markup'];
250         else
251             $this->_markup = 1;
252     }
253
254     function _transform($text) {
255         include_once('lib/BlockParser.php');
256         return TransformText($text, $this->_markup);
257     }
258
259     /** Transform the page text.
260      *
261      * @param string $text  The raw page content (e.g. wiki-text).
262      * @return XmlContent   Transformed content.
263      */
264     function format($text) {
265         trigger_error("pure virtual", E_USER_ERROR);
266     }
267 }
268
269 class PageFormatter_wikitext extends PageFormatter 
270 {
271     function format($text) {
272         return HTML::div(array('class' => 'wikitext'),
273                          $this->_transform($text));
274     }
275 }
276
277 class PageFormatter_interwikimap extends PageFormatter
278 {
279     function format($text) {
280         return HTML::div(array('class' => 'wikitext'),
281                          $this->_transform($this->_getHeader($text)),
282                          $this->_formatMap(),
283                          $this->_transform($this->_getFooter($text)));
284     }
285
286     function _getHeader($text) {
287         return preg_replace('/<verbatim>.*/s', '', $text);
288     }
289
290     function _getFooter($text) {
291         return preg_replace('@.*?(</verbatim>|\Z)@s', '', $text, 1);
292     }
293     
294     function _getMap() {
295         $map = PageType_interwikimap::getMap();
296         return $map->_map;
297     }
298     
299     function _formatMap() {
300         $map = $this->_getMap();
301         if (!$map)
302             return HTML::p("<No map found>"); // Shouldn't happen.
303
304         global $request;
305         $dbi = $request->getDbh();
306
307         $mon_attr = array('class' => 'interwiki-moniker');
308         $url_attr = array('class' => 'interwiki-url');
309         
310         $thead = HTML::thead(HTML::tr(HTML::th($mon_attr, _("Moniker")),
311                                       HTML::th($url_attr, _("InterWiki Address"))));
312         foreach ($map as $moniker => $interurl) {
313             $rows[] = HTML::tr(HTML::td($mon_attr, new Cached_WikiLinkIfKnown($moniker)),
314                                HTML::td($url_attr, HTML::tt($interurl)));
315         }
316         
317         return HTML::table(array('class' => 'interwiki-map'),
318                            $thead,
319                            HTML::tbody(false, $rows));
320     }
321 }
322
323 class FakePageRevision {
324     function FakePageRevision($meta) {
325         $this->_meta = $meta;
326     }
327
328     function get($key) {
329         if (empty($this->_meta[$key]))
330             return false;
331         return $this->_meta[$key];
332     }
333 }
334         
335 class PageFormatter_attach extends PageFormatter
336 {
337     var $type, $prefix;
338     
339     // Display templated contents for wikiblog, comment and wikiforum
340     function format($text) {
341         if (empty($this->type))
342             trigger_error('PageFormatter_attach->format: $type missing');
343         include_once('lib/Template.php');
344         global $request;
345         $tokens['CONTENT'] = $this->_transform($text);
346         $tokens['page'] = $this->_page;
347         $tokens['rev'] = new FakePageRevision($this->_meta);
348
349         $name = new WikiPageName($this->_page->getName());
350         $tokens[$this->prefix."_PARENT"] = $name->getParent();
351
352         $meta = $this->_meta[$this->type];
353         foreach(array('ctime', 'creator', 'creator_id') as $key)
354             $tokens[$this->prefix . "_" . strtoupper($key)] = $meta[$key];
355         
356         return new Template($this->type, $request, $tokens);
357     }
358 }
359
360 class PageFormatter_wikiblog extends PageFormatter_attach {
361     var $type = 'wikiblog', $prefix = "BLOG";
362 }
363 class PageFormatter_comment extends PageFormatter_attach {
364     var $type = 'comment', $prefix = "COMMENT";
365 }
366 class PageFormatter_wikiforum extends PageFormatter_attach {
367     var $type = 'wikiforum', $prefix = "FORUM";
368 }
369
370 /** wikiabuse for htmlarea editing. not yet used.  
371  *
372  * Warning! Once a page is edited with a htmlarea like control it is
373  * stored in HTML and cannot be converted back to WikiText as long as
374  * we have no HTML => WikiText or any other interim format (WikiExchangeFormat e.g. Xml) 
375  * converter. So it has a viral effect and certain plugins will not work anymore.
376  * But a lot of wikiusers seem to like it.
377  */
378 class PageFormatter_html extends PageFormatter
379 {
380     function _transform($text) {
381         return $text;
382     }
383     function format($text) {
384         return $text;
385     }
386 }
387
388 /**
389  *  FIXME. not yet used
390  */
391 class PageFormatter_pdf extends PageFormatter
392 {
393
394     function _transform($text) {
395         include_once('lib/BlockParser.php');
396         return TransformText($text, $this->_markup);
397     }
398
399     // one page or set of pages?
400     // here we try to format only a single page
401     function format($text) {
402         include_once('lib/Template.php');
403         global $request;
404         $tokens['page']    = $this->_page;
405         $tokens['CONTENT'] = $this->_transform($text);
406         $pagename = $this->_page->getName();
407
408         // This is a XmlElement tree, which must be converted to PDF
409
410         // We can make use of several pdf extensions. This one - fpdf
411         // - is pure php and very easy, but looks quite ugly and has a
412         // terrible interface, as terrible as most of the othes. 
413         // The closest to HTML is htmldoc which needs an external cgi
414         // binary.
415         // We use a custom HTML->PDF class converter from PHPWebthings
416         // to be able to use templates for PDF.
417         require_once('lib/fpdf.php');
418         require_once('lib/pdf.php');
419
420         $pdf = new PDF();
421         $pdf->SetTitle($pagename);
422         $pdf->SetAuthor($this->_page->get('author'));
423         $pdf->SetCreator(WikiUrl($pagename,false,1));
424         $pdf->AliasNbPages();
425         $pdf->AddPage();
426         //TODO: define fonts
427         $pdf->SetFont('Times','',12);
428         //$pdf->SetFont('Arial','B',16);
429
430         // PDF pagelayout from a special template
431         $template = new Template('pdf', $request, $tokens);
432         $pdf->ConvertFromHTML($template);
433
434         // specify a filename and bool for download. 
435         // if empty it must be the first template used, no headers already sent.
436         $pdf->Output($pagename.".pdf",/*download*/ true);
437
438         // Output([string name [, string dest]])
439         return $pdf;
440     }
441 }
442
443 // Local Variables:
444 // mode: php
445 // tab-width: 8
446 // c-basic-offset: 4
447 // c-hanging-comment-ender-p: nil
448 // indent-tabs-mode: nil
449 // End:
450 ?>