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