]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/PageType.php
Be consistent in function declarations
[SourceForge/phpwiki.git] / lib / PageType.php
1 <?php // -*-php-*-
2 // $Id$
3 /*
4  * Copyright 1999,2000,2001,2002,2003,2004,2005,2006 $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 along
19  * with PhpWiki; if not, write to the Free Software Foundation, Inc.,
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 = false;
38         if ($type_override)
39             $pagetype = $type_override;
40         elseif (isset($meta['pagetype']))
41             $pagetype = $meta['pagetype'];
42     $this->_type = PageType::GetPageType($pagetype);
43     $this->CacheableMarkup($this->_type->transform($page, $text, $meta),
44                                $page->getName());
45     }
46
47     function getType() {
48     return $this->_type;
49     }
50 }
51
52 /**
53  * A page type descriptor.
54  *
55  * Encapsulate information about page types.
56  *
57  * Currently the only information encapsulated is how to format
58  * the specific page type.  In the future or capabilities may be
59  * added, e.g. the abilities to edit different page types (differently.)
60  * e.g. Support for the javascript htmlarea editor, which can only edit
61  * pure HTML.
62  *
63  * IMPORTANT NOTE: Since the whole PageType class gets stored (serialized)
64  * as of the cached marked-up page, it is important that the PageType classes
65  * not have large amounts of class data.  (No class data is even better.)
66  */
67 class PageType {
68     /**
69      * Get a page type descriptor.
70      *
71      * This is a static member function.
72      *
73      * @param string $pagetype  Name of the page type.
74      * @return PageType  An object which is a subclass of PageType.
75      */
76     function GetPageType ($name=false) {
77         if (!$name)
78             $name = 'wikitext';
79         $class = "PageType_" . (string)$name;
80         if (class_exists($class))
81             return new $class;
82         trigger_error(sprintf("PageType '%s' unknown", (string)$name),
83                       E_USER_WARNING);
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_MediaWiki extends PageType {}
122
123 /* To prevent from PHP5 Fatal error: Using $this when not in object context */
124 function getInterwikiMap ($pagetext=false, $force=false) {
125     static $map;
126     if (empty($map) or $force)
127         $map = new PageType_interwikimap($pagetext);
128     return $map;
129 }
130
131 class PageType_interwikimap extends PageType
132 {
133     function PageType_interwikimap($pagetext = false) {
134         if (!$pagetext) {
135             $dbi = $GLOBALS['request']->getDbh();
136             $page = $dbi->getPage(_("InterWikiMap"));
137             if ($page->get('locked')) {
138                 $current = $page->getCurrentRevision();
139                 $pagetext = $current->getPackedContent();
140                 $intermap = $this->_getMapFromWikiText($pagetext);
141             } elseif ($page->exists()) {
142                 trigger_error(_("WARNING: InterWikiMap page is unlocked, so not using those links."));
143                 $intermap = false;
144             }
145             else
146                 $intermap = false;
147         } else {
148             $intermap = $this->_getMapFromWikiText($pagetext);
149         }
150         if (!$intermap && defined('INTERWIKI_MAP_FILE'))
151             $intermap = $this->_getMapFromFile(INTERWIKI_MAP_FILE);
152
153         $this->_map = $this->_parseMap($intermap);
154         $this->_regexp = $this->_getRegexp();
155     }
156
157     function GetMap ($pagetext = false) {
158         /*PHP5 Fatal error: Using $this when not in object context */
159         if (empty($this->_map)) {
160             $map = new PageType_interwikimap($pagetext);
161             return $map;
162         } else {
163             return $this;
164         }
165     }
166
167     function getRegexp() {
168         return $this->_regexp;
169     }
170
171     function link ($link, $linktext = false) {
172     global $WikiTheme;
173         list ($moniker, $page) = explode (":", $link, 2);
174
175         if (!isset($this->_map[$moniker])) {
176             return HTML::span(array('class' => 'bad-interwiki'),
177                               $linktext ? $linktext : $link);
178         }
179
180         $url = $this->_map[$moniker];
181     // localize Upload:links for WIKIDUMP
182     if (!empty($WikiTheme->DUMP_MODE) and $moniker == 'Upload') {
183         global $request;
184         include_once("lib/config.php");
185         $url = getUploadFilePath();
186         // calculate to a relative local path to /uploads for pdf images.
187         $doc_root = $request->get("DOCUMENT_ROOT");
188             $ldir = NormalizeLocalFileName($url);
189             $wikiroot = NormalizeLocalFileName('');
190             if (isWindows()) {
191                 $ldir = strtolower($ldir);
192                 $doc_root = strtolower($doc_root);
193                 $wikiroot = strtolower($wikiroot);
194             }
195             if (string_starts_with($ldir, $doc_root)) {
196             $link_prefix = substr($url, strlen($doc_root));
197             } elseif (string_starts_with($ldir, $wikiroot)) {
198             $link_prefix = NormalizeWebFileName(substr($url, strlen($wikiroot)));
199         }
200     }
201
202         // Urlencode page only if it's a query arg.
203         // FIXME: this is a somewhat broken heuristic.
204         if ($moniker == 'Upload') {
205             $page_enc = $page;
206             $page = rawurldecode($page);
207         } else {
208             $page_enc = strstr($url, '?') ? rawurlencode($page) : $page;
209         }
210         if (strstr($url, '%s'))
211             $url = sprintf($url, $page_enc);
212         else
213             $url .= $page_enc;
214
215         $link = HTML::a(array('href' => $url));
216
217     if (!$linktext) {
218             $link->pushContent(PossiblyGlueIconToText('interwiki', "$moniker:"),
219                                HTML::span(array('class' => 'wikipage'), $page));
220             $link->setAttr('class', 'interwiki');
221         }
222         else {
223             $link->pushContent(PossiblyGlueIconToText('interwiki', $linktext));
224             $link->setAttr('class', 'named-interwiki');
225         }
226
227         return $link;
228     }
229
230
231     function _parseMap ($text) {
232         if (!preg_match_all("/^\s*(\S+)\s+(.+)$/m",
233                             $text, $matches, PREG_SET_ORDER))
234             return false;
235
236         foreach ($matches as $m) {
237             $map[$m[1]] = $m[2];
238         }
239
240         // Add virtual monikers: "Upload:" "Talk:" "User:", ":"
241         // and expand special variables %u, %b, %d
242
243         // Upload: Should be expanded later to user-specific upload dirs.
244         // In the Upload plugin, not here: Upload:ReiniUrban/uploaded-file.png
245         if (empty($map['Upload'])) {
246             $map['Upload'] = getUploadDataPath();
247         }
248         // User:ReiniUrban => ReiniUrban or Users/ReiniUrban
249         // Can be easily overriden by a customized InterWikiMap:
250         //   User Users/%s
251         if (empty($map["User"])) {
252             $map["User"] = "%s";
253         }
254         // Talk:UserName => UserName/Discussion
255         // Talk:PageName => PageName/Discussion as default, which might be overridden
256         if (empty($map["Talk"])) {
257             $pagename = $GLOBALS['request']->getArg('pagename');
258             // against PageName/Discussion/Discussion
259             if (string_ends_with($pagename, SUBPAGE_SEPARATOR._("Discussion")))
260                 $map["Talk"] = "%s";
261             else
262                 $map["Talk"] = "%s".SUBPAGE_SEPARATOR._("Discussion");
263         }
264
265         foreach (array('Upload','User','Talk') as $special) {
266             // Expand special variables:
267             //   %u => username
268             //   %b => wikibaseurl
269             //   %d => iso8601 DateTime
270             // %s is expanded later to the pagename
271             if (strstr($map[$special], '%u'))
272                 $map[$special] = str_replace($map[$special],
273                                              '%u',
274                                              $GLOBALS['request']->_user->_userid);
275             if (strstr($map[$special], '%b'))
276                 $map[$special] = str_replace($map[$special],
277                                              '%b',
278                                              PHPWIKI_BASE_URL);
279             if (strstr($map[$special], '%d'))
280                 $map[$special] = str_replace($map[$special],
281                                              '%d',
282                                              // such as 2003-01-11T14:03:02+00:00
283                                              Iso8601DateTime());
284         }
285
286         return $map;
287     }
288
289     function _getMapFromWikiText ($pagetext) {
290         if (preg_match('|^<verbatim>\n(.*)^</verbatim>|ms', $pagetext, $m)) {
291             return $m[1];
292         }
293         return false;
294     }
295
296     function _getMapFromFile ($filename) {
297         if (defined('WARN_NONPUBLIC_INTERWIKIMAP') and WARN_NONPUBLIC_INTERWIKIMAP) {
298             $error_html = sprintf(_("Loading InterWikiMap from external file %s."),
299                                   $filename);
300             trigger_error( $error_html, E_USER_NOTICE );
301         }
302         if (!file_exists($filename)) {
303             $finder = new FileFinder();
304             $filename = $finder->findFile(INTERWIKI_MAP_FILE);
305         }
306         @$fd = fopen ($filename, "rb");
307         @$data = fread ($fd, filesize($filename));
308         @fclose ($fd);
309
310         return $data;
311     }
312
313     function _getRegexp () {
314         if (!$this->_map)
315             return '(?:(?!a)a)'; //  Never matches.
316
317         foreach (array_keys($this->_map) as $moniker)
318             $qkeys[] = preg_quote($moniker, '/');
319         return "(?:" . join("|", $qkeys) . ")";
320     }
321 }
322
323
324 /** How to transform text.
325  */
326 class PageFormatter {
327     /** Constructor.
328      *
329      * @param WikiDB_Page $page
330      * @param hash $meta Version meta-data.
331      */
332     function PageFormatter(&$page, $meta) {
333         $this->_page = $page;
334     $this->_meta = $meta;
335     if (!empty($meta['markup']))
336         $this->_markup = $meta['markup'];
337     else
338         $this->_markup = 2; // dump used old-markup as empty.
339         // FIXME: To be able to restore old plain-backups we should keep markup 1 as default.
340         // New policy: default = new markup (old crashes quite often)
341     }
342
343     function _transform($text) {
344     include_once('lib/BlockParser.php');
345     return TransformText($text, $this->_markup);
346     }
347
348     /** Transform the page text.
349      *
350      * @param string $text  The raw page content (e.g. wiki-text).
351      * @return XmlContent   Transformed content.
352      */
353     function format($text) {
354         trigger_error("pure virtual", E_USER_ERROR);
355     }
356 }
357
358 class PageFormatter_wikitext extends PageFormatter
359 {
360     function format($text) {
361         return HTML::div(array('class' => 'wikitext'),
362              $this->_transform($text));
363     }
364 }
365
366 class PageFormatter_interwikimap extends PageFormatter
367 {
368     function format($text) {
369     return HTML::div(array('class' => 'wikitext'),
370              $this->_transform($this->_getHeader($text)),
371              $this->_formatMap($text),
372              $this->_transform($this->_getFooter($text)));
373     }
374
375     function _getHeader($text) {
376     return preg_replace('/<verbatim>.*/s', '', $text);
377     }
378
379     function _getFooter($text) {
380     return preg_replace('@.*?(</verbatim>|\Z)@s', '', $text, 1);
381     }
382
383     function _getMap($pagetext) {
384         $map = getInterwikiMap($pagetext, 'force');
385         return $map->_map;
386     }
387
388     function _formatMap($pagetext) {
389     $map = $this->_getMap($pagetext);
390     if (!$map)
391         return HTML::p("<No interwiki map found>"); // Shouldn't happen.
392
393         $mon_attr = array('class' => 'interwiki-moniker');
394         $url_attr = array('class' => 'interwiki-url');
395
396         $thead = HTML::thead(HTML::tr(HTML::th($mon_attr, _("Moniker")),
397                       HTML::th($url_attr, _("InterWiki Address"))));
398     foreach ($map as $moniker => $interurl) {
399         $rows[] = HTML::tr(HTML::td($mon_attr, new Cached_WikiLinkIfKnown($moniker)),
400                    HTML::td($url_attr, HTML::tt($interurl)));
401         }
402
403     return HTML::table(array('class' => 'interwiki-map'),
404                $thead,
405                HTML::tbody(false, $rows));
406     }
407 }
408
409 class FakePageRevision {
410     function FakePageRevision($meta) {
411         $this->_meta = $meta;
412     }
413
414     function get($key) {
415         if (empty($this->_meta[$key]))
416             return false;
417         return $this->_meta[$key];
418     }
419 }
420
421 // abstract base class
422 class PageFormatter_attach extends PageFormatter
423 {
424     var $type, $prefix;
425
426     // Display templated contents for wikiblog, comment and wikiforum
427     function format($text) {
428         if (empty($this->type))
429             trigger_error('PageFormatter_attach->format: $type missing');
430         include_once('lib/Template.php');
431         global $request;
432         $tokens['CONTENT'] = $this->_transform($text);
433         $tokens['page'] = $this->_page;
434         $tokens['rev'] = new FakePageRevision($this->_meta);
435
436         $name = new WikiPageName($this->_page->getName());
437         $tokens[$this->prefix."_PARENT"] = $name->getParent();
438
439         $meta = $this->_meta[$this->type];
440         foreach(array('ctime', 'creator', 'creator_id') as $key)
441             $tokens[$this->prefix . "_" . strtoupper($key)] = $meta[$key];
442
443         return new Template($this->type, $request, $tokens);
444     }
445 }
446
447 class PageFormatter_wikiblog extends PageFormatter_attach {
448     var $type = 'wikiblog', $prefix = "BLOG";
449 }
450 class PageFormatter_comment extends PageFormatter_attach {
451     var $type = 'comment', $prefix = "COMMENT";
452 }
453 class PageFormatter_wikiforum extends PageFormatter_attach {
454     var $type = 'wikiforum', $prefix = "FORUM";
455 }
456
457 /** wikiabuse for htmlarea editing. not yet used.
458  *
459  * Warning! Once a page is edited with a htmlarea like control it is
460  * stored in HTML and cannot be converted back to WikiText as long as
461  * we have no HTML => WikiText or any other interim format (WikiExchangeFormat e.g. XML)
462  * converter. See lib/HtmlParser.php for ongoing work on that.
463  * So it has a viral effect and certain plugins will not work anymore.
464  * But a lot of wikiusers seem to like it.
465  */
466 class PageFormatter_html extends PageFormatter
467 {
468     function _transform($text) {
469         return $text;
470     }
471     function format($text) {
472         return $text;
473     }
474 }
475
476 /**
477  *  FIXME. not yet used
478  */
479 class PageFormatter_pdf extends PageFormatter
480 {
481
482     function _transform($text) {
483     include_once('lib/BlockParser.php');
484     return TransformText($text, $this->_markup);
485     }
486
487     // one page or set of pages?
488     // here we try to format only a single page
489     function format($text) {
490         include_once('lib/Template.php');
491         global $request;
492         $tokens['page']    = $this->_page;
493         $tokens['CONTENT'] = $this->_transform($text);
494         $pagename = $this->_page->getName();
495
496         // This is a XmlElement tree, which must be converted to PDF
497
498         // We can make use of several pdf extensions. This one - fpdf
499         // - is pure php and very easy, but looks quite ugly and has a
500         // terrible interface, as terrible as most of the othes.
501         // The closest to HTML is htmldoc which needs an external cgi
502         // binary.
503         // We use a custom HTML->PDF class converter from PHPWebthings
504         // to be able to use templates for PDF.
505         require_once('lib/fpdf.php');
506         require_once('lib/pdf.php');
507
508         $pdf = new PDF();
509         $pdf->SetTitle($pagename);
510         $pdf->SetAuthor($this->_page->get('author'));
511         $pdf->SetCreator(WikiURL($pagename,false,1));
512         $pdf->AliasNbPages();
513         $pdf->AddPage();
514         //TODO: define fonts
515         $pdf->SetFont('Times','',12);
516         //$pdf->SetFont('Arial','B',16);
517
518         // PDF pagelayout from a special template
519         $template = new Template('pdf', $request, $tokens);
520         $pdf->ConvertFromHTML($template);
521
522         // specify filename, destination
523         $pdf->Output($pagename.".pdf",'I'); // I for stdin or D for download
524
525         // Output([string name [, string dest]])
526         return $pdf;
527     }
528 }
529
530 class PageFormatter_MediaWiki extends PageFormatter
531 {
532     function _transform($text) {
533     include_once('lib/BlockParser.php');
534     // Expand leading tabs.
535     $text = expand_tabs($text);
536
537         $input = new BlockParser_Input($text);
538         $output = $this->ParsedBlock($input);
539     return new XmlContent($output->getContent());
540     }
541
542     function format($text) {
543     return HTML::div(array('class' => 'wikitext'),
544              $this->_transform($text));
545     }
546 }
547
548
549 // Local Variables:
550 // mode: php
551 // tab-width: 8
552 // c-basic-offset: 4
553 // c-hanging-comment-ender-p: nil
554 // indent-tabs-mode: nil
555 // End:
556 ?>