]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/PageType.php
Use UTF-8 for calendar-de.js
[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 abstract class PageType
70 {
71     /**
72      * Get a page type descriptor.
73      *
74      * @param  string   $name Name of the page type.
75      * @return PageType An object which is a subclass of PageType.
76      */
77     static function GetPageType($name = '')
78     {
79         if (!$name)
80             $name = 'wikitext';
81         $class = "PageType_" . (string)$name;
82         if (class_exists($class))
83             return new $class;
84         trigger_error(sprintf("PageType ā€œ%sā€ unknown", (string)$name),
85             E_USER_WARNING);
86         return new PageType_wikitext;
87     }
88
89     /**
90      * Get the name of this page type.
91      *
92      * @return string Page type name.
93      */
94     function getName()
95     {
96         if (!preg_match('/^PageType_(.+)$/i', get_class($this), $m))
97             trigger_error("Bad class name for formatter(?)", E_USER_ERROR);
98         return $m[1];
99     }
100
101     /**
102      * Transform page text.
103      *
104      * @param  WikiDB_Page $page
105      * @param  string      $text
106      * @param  array       $meta Version meta-data
107      * @return XmlContent  The transformed page text.
108      */
109     function transform(&$page, &$text, $meta)
110     {
111         $fmt_class = 'PageFormatter_' . $this->getName();
112         $formatter = new $fmt_class($page, $meta);
113         return $formatter->format($text);
114     }
115 }
116
117 class PageType_wikitext extends PageType
118 {
119 }
120
121 class PageType_html extends PageType
122 {
123 }
124
125 class PageType_wikiblog extends PageType
126 {
127 }
128
129 class PageType_comment extends PageType
130 {
131 }
132
133 class PageType_wikiforum extends PageType
134 {
135 }
136
137 class PageType_MediaWiki extends PageType
138 {
139 }
140
141 /* To prevent from PHP5 Fatal error: Using $this when not in object context */
142 function getInterwikiMap($pagetext = false, $force = false)
143 {
144     static $map;
145     if (empty($map) or $force)
146         $map = new PageType_interwikimap($pagetext);
147     return $map;
148 }
149
150 class PageType_interwikimap extends PageType
151 {
152     function __construct($pagetext = false)
153     {
154         /**
155          * @var WikiRequest $request
156          */
157         global $request;
158
159         if (!$pagetext) {
160             $dbi = $request->getDbh();
161             $page = $dbi->getPage(__("InterWikiMap"));
162             if ($page->get('locked')) {
163                 $current = $page->getCurrentRevision();
164                 $pagetext = $current->getPackedContent();
165                 $intermap = $this->_getMapFromWikiText($pagetext);
166             } elseif ($page->exists()) {
167                 trigger_error(_("WARNING: InterWikiMap page is unlocked, so not using those links."));
168                 $intermap = false;
169             } else
170                 $intermap = false;
171         } else {
172             $intermap = $this->_getMapFromWikiText($pagetext);
173         }
174         if (!$intermap && defined('INTERWIKI_MAP_FILE'))
175             $intermap = $this->_getMapFromFile(INTERWIKI_MAP_FILE);
176
177         $this->_map = $this->_parseMap($intermap);
178         $this->_regexp = $this->_getRegexp();
179     }
180
181     function GetMap($pagetext = false)
182     {
183         /*PHP5 Fatal error: Using $this when not in object context */
184         if (empty($this->_map)) {
185             $map = new PageType_interwikimap($pagetext);
186             return $map;
187         } else {
188             return $this;
189         }
190     }
191
192     function getRegexp()
193     {
194         return $this->_regexp;
195     }
196
197     function link($link, $linktext = false)
198     {
199         global $WikiTheme;
200         list ($moniker, $page) = explode(":", $link, 2);
201
202         if (!isset($this->_map[$moniker])) {
203             return HTML::span(array('class' => 'bad-interwiki'),
204                 $linktext ? $linktext : $link);
205         }
206
207         $url = $this->_map[$moniker];
208         // localize Upload:links for WIKIDUMP
209         if (!empty($WikiTheme->DUMP_MODE) and $moniker == 'Upload') {
210             global $request;
211             include_once 'lib/config.php';
212             $url = getUploadFilePath();
213             // calculate to a relative local path to /uploads for PDF images.
214             $doc_root = $request->get("DOCUMENT_ROOT");
215             $ldir = NormalizeLocalFileName($url);
216             $wikiroot = NormalizeLocalFileName('');
217             if (isWindows()) {
218                 $ldir = strtolower($ldir);
219                 $doc_root = strtolower($doc_root);
220                 $wikiroot = strtolower($wikiroot);
221             }
222             if (string_starts_with($ldir, $doc_root)) {
223                 $link_prefix = substr($url, strlen($doc_root));
224             } elseif (string_starts_with($ldir, $wikiroot)) {
225                 $link_prefix = NormalizeWebFileName(substr($url, strlen($wikiroot)));
226             }
227         }
228
229         // Urlencode page only if it's a query arg.
230         // FIXME: this is a somewhat broken heuristic.
231         if ($moniker == 'Upload') {
232             $page_enc = $page;
233             $page = rawurldecode($page);
234         } else {
235             $page_enc = strstr($url, '?') ? rawurlencode($page) : $page;
236         }
237         if (strstr($url, '%s'))
238             $url = sprintf($url, $page_enc);
239         else
240             $url .= $page_enc;
241
242         // Encode spaces in '[[Help:Reini Urban]]'
243         // but not in '[[Upload:logo.jpg size=40x25 align=center]]'
244         if ($moniker != 'Upload') {
245             $url = str_replace(' ', '%20', $url);
246         }
247
248         $link = HTML::a(array('href' => $url));
249
250         if (!$linktext) {
251             $link->pushContent(PossiblyGlueIconToText('interwiki', "$moniker:"),
252                 HTML::span(array('class' => 'wikipage'), $page));
253             $link->setAttr('class', 'interwiki');
254         } else {
255             $link->pushContent(PossiblyGlueIconToText('interwiki', $linktext));
256             $link->setAttr('class', 'named-interwiki');
257         }
258
259         return $link;
260     }
261
262     private function _parseMap($text)
263     {
264         /**
265          * @var WikiRequest $request
266          */
267         global $request;
268
269         if (!preg_match_all("/^\s*(\S+)\s+(.+)$/m",
270             $text, $matches, PREG_SET_ORDER)
271         )
272             return false;
273
274         foreach ($matches as $m) {
275             $map[$m[1]] = $m[2];
276         }
277
278         // Add virtual monikers: "Upload:" "Talk:" "User:", ":"
279         // and expand special variables %u, %b, %d
280
281         // Upload: Should be expanded later to user-specific upload dirs.
282         // In the Upload plugin, not here: Upload:ReiniUrban/uploaded-file.png
283         if (empty($map['Upload'])) {
284             $map['Upload'] = getUploadDataPath();
285         }
286         // User:ReiniUrban => ReiniUrban or Users/ReiniUrban
287         // Can be easily overriden by a customized InterWikiMap:
288         //   User Users/%s
289         if (empty($map["User"])) {
290             $map["User"] = "%s";
291         }
292         // Talk:UserName => UserName/Discussion
293         // Talk:PageName => PageName/Discussion as default, which might be overridden
294         if (empty($map["Talk"])) {
295             $pagename = $request->getArg('pagename');
296             // against PageName/Discussion/Discussion
297             if (string_ends_with($pagename, '/' . _("Discussion")))
298                 $map["Talk"] = "%s";
299             else
300                 $map["Talk"] = "%s" . '/' . _("Discussion");
301         }
302
303         foreach (array('Upload', 'User', 'Talk') as $special) {
304             // Expand special variables:
305             //   %u => username
306             //   %b => wikibaseurl
307             //   %d => iso8601 DateTime
308             // %s is expanded later to the pagename
309             if (strstr($map[$special], '%u'))
310                 $map[$special] = str_replace($map[$special],
311                     '%u',
312                     $request->_user->_userid);
313             if (strstr($map[$special], '%b'))
314                 $map[$special] = str_replace($map[$special],
315                     '%b',
316                     PHPWIKI_BASE_URL);
317             if (strstr($map[$special], '%d'))
318                 $map[$special] = str_replace($map[$special],
319                     '%d',
320                     // such as 2003-01-11T14:03:02+00:00
321                     Iso8601DateTime());
322         }
323
324         return $map;
325     }
326
327     private function _getMapFromWikiText($pagetext)
328     {
329         if (preg_match('|^<verbatim>\n(.*)^</verbatim>|ms', $pagetext, $m)) {
330             return $m[1];
331         }
332         return false;
333     }
334
335     private function _getMapFromFile($filename)
336     {
337         if (defined('WARN_NONPUBLIC_INTERWIKIMAP') and WARN_NONPUBLIC_INTERWIKIMAP) {
338             $error_html = sprintf(_("Loading InterWikiMap from external file %s."),
339                 $filename);
340             trigger_error($error_html, E_USER_NOTICE);
341         }
342         if (!file_exists($filename)) {
343             $finder = new FileFinder();
344             $filename = $finder->findFile(INTERWIKI_MAP_FILE);
345         }
346         @$fd = fopen($filename, "rb");
347         @$data = fread($fd, filesize($filename));
348         @fclose($fd);
349
350         return $data;
351     }
352
353     private function _getRegexp()
354     {
355         if (!$this->_map)
356             return '(?:(?!a)a)'; //  Never matches.
357
358         $qkeys = array();
359         foreach (array_keys($this->_map) as $moniker)
360             $qkeys[] = preg_quote($moniker, '/');
361         return "(?:" . join("|", $qkeys) . ")";
362     }
363 }
364
365 /** How to transform text.
366  */
367 abstract class PageFormatter
368 {
369     /**
370      * @param WikiDB_Page $page
371      * @param array       $meta Version meta-data hash.
372      */
373     function __construct(&$page, $meta)
374     {
375         $this->_page = $page;
376         $this->_meta = $meta;
377     }
378
379     function _transform($text)
380     {
381         include_once 'lib/BlockParser.php';
382         return TransformText($text);
383     }
384
385     /** Transform the page text.
386      *
387      * @param  string     $text The raw page content (e.g. wiki-text).
388      * @return XmlContent Transformed content.
389      */
390     abstract function format($text);
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     protected function _getHeader($text)
413     {
414         return preg_replace('/<verbatim>.*/s', '', $text);
415     }
416
417     protected function _getFooter($text)
418     {
419         return preg_replace('@.*?(</verbatim>|\Z)@s', '', $text, 1);
420     }
421
422     protected function _getMap($pagetext)
423     {
424         $map = getInterwikiMap($pagetext, 'force');
425         return $map->_map;
426     }
427
428     protected 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         $rows = array();
440         foreach ($map as $moniker => $interurl) {
441             $rows[] = HTML::tr(HTML::td($mon_attr, new Cached_WikiLinkIfKnown($moniker)),
442                 HTML::td($url_attr, HTML::samp($interurl)));
443         }
444
445         return HTML::table(array('class' => 'interwiki-map'),
446             $thead,
447             HTML::tbody(false, $rows));
448     }
449 }
450
451 class FakePageRevision
452 {
453     function __construct($meta)
454     {
455         $this->_meta = $meta;
456     }
457
458     function get($key)
459     {
460         if (empty($this->_meta[$key]))
461             return false;
462         return $this->_meta[$key];
463     }
464 }
465
466 // abstract base class
467 class PageFormatter_attach extends PageFormatter
468 {
469     public $type, $prefix;
470
471     // Display templated contents for wikiblog, comment and wikiforum
472     function format($text)
473     {
474         if (empty($this->type))
475             trigger_error('PageFormatter_attach->format: $type missing');
476         include_once 'lib/Template.php';
477         global $request;
478         $tokens['CONTENT'] = $this->_transform($text);
479         $tokens['page'] = $this->_page;
480         $tokens['rev'] = new FakePageRevision($this->_meta);
481
482         $name = new WikiPageName($this->_page->getName());
483         $tokens[$this->prefix . "_PARENT"] = $name->getParent();
484
485         $meta = $this->_meta[$this->type];
486         foreach (array('ctime', 'creator', 'creator_id') as $key)
487             $tokens[$this->prefix . "_" . strtoupper($key)] = $meta[$key];
488
489         return new Template($this->type, $request, $tokens);
490     }
491 }
492
493 class PageFormatter_wikiblog extends PageFormatter_attach
494 {
495     public $type = 'wikiblog', $prefix = "BLOG";
496 }
497
498 class PageFormatter_comment extends PageFormatter_attach
499 {
500     public $type = 'comment', $prefix = "COMMENT";
501 }
502
503 class PageFormatter_wikiforum extends PageFormatter_attach
504 {
505     public $type = 'wikiforum', $prefix = "FORUM";
506 }
507
508 /** wikiabuse for htmlarea editing. not yet used.
509  *
510  * Warning! Once a page is edited with a htmlarea like control it is
511  * stored in HTML and cannot be converted back to WikiText as long as
512  * we have no HTML => WikiText or any other interim format (WikiExchangeFormat e.g. XML)
513  * converter. See lib/HtmlParser.php for ongoing work on that.
514  * So it has a viral effect and certain plugins will not work anymore.
515  * But a lot of wikiusers seem to like it.
516  */
517 class PageFormatter_html extends PageFormatter
518 {
519     function _transform($text)
520     {
521         return $text;
522     }
523
524     function format($text)
525     {
526         return $text;
527     }
528 }
529
530 class PageFormatter_MediaWiki extends PageFormatter
531 {
532     function _transform($text)
533     {
534         include_once 'lib/BlockParser.php';
535         // Expand leading tabs.
536         $text = expand_tabs($text);
537
538         $input = new BlockParser_Input($text);
539         $output = $this->ParsedBlock($input);
540         return new XmlContent($output->getContent());
541     }
542
543     function format($text)
544     {
545         return HTML::div(array('class' => 'wikitext'),
546             $this->_transform($text));
547     }
548 }
549
550 // Local Variables:
551 // mode: php
552 // tab-width: 8
553 // c-basic-offset: 4
554 // c-hanging-comment-ender-p: nil
555 // indent-tabs-mode: nil
556 // End: