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