4 * Copyright 1999,2000,2001,2002,2003,2004,2005,2006 $ThePhpWikiProgrammingTeam
6 * This file is part of PhpWiki.
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.
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.
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.
23 require_once 'lib/CachedMarkup.php';
25 /** A cacheable formatted wiki page.
27 class TransformedText extends CacheableMarkup
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.
37 function TransformedText($page, $text, $meta, $type_override = false)
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),
56 * A page type descriptor.
58 * Encapsulate information about page types.
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
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.)
73 * Get a page type descriptor.
75 * This is a static member function.
77 * @param string $pagetype Name of the page type.
78 * @return PageType An object which is a subclass of PageType.
80 function GetPageType($name = false)
84 $class = "PageType_" . (string)$name;
85 if (class_exists($class))
87 trigger_error(sprintf("PageType '%s' unknown", (string)$name),
89 return new PageType_wikitext;
93 * Get the name of this page type.
95 * @return string Page type name.
99 if (!preg_match('/^PageType_(.+)$/i', get_class($this), $m))
100 trigger_error("Bad class name for formatter(?)", E_USER_ERROR);
105 * Transform page text.
107 * @param WikiDB_Page $page
108 * @param string $text
109 * @param hash $meta Version meta-data
110 * @return XmlContent The transformed page text.
112 function transform(&$page, &$text, $meta)
114 $fmt_class = 'PageFormatter_' . $this->getName();
115 $formatter = new $fmt_class($page, $meta);
116 return $formatter->format($text);
120 class PageType_wikitext extends PageType
124 class PageType_html extends PageType
128 class PageType_pdf extends PageType
132 class PageType_wikiblog extends PageType
136 class PageType_comment extends PageType
140 class PageType_wikiforum extends PageType
144 class PageType_MediaWiki extends PageType
148 /* To prevent from PHP5 Fatal error: Using $this when not in object context */
149 function getInterwikiMap($pagetext = false, $force = false)
152 if (empty($map) or $force)
153 $map = new PageType_interwikimap($pagetext);
157 class PageType_interwikimap extends PageType
159 function PageType_interwikimap($pagetext = false)
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."));
174 $intermap = $this->_getMapFromWikiText($pagetext);
176 if (!$intermap && defined('INTERWIKI_MAP_FILE'))
177 $intermap = $this->_getMapFromFile(INTERWIKI_MAP_FILE);
179 $this->_map = $this->_parseMap($intermap);
180 $this->_regexp = $this->_getRegexp();
183 function GetMap($pagetext = false)
185 /*PHP5 Fatal error: Using $this when not in object context */
186 if (empty($this->_map)) {
187 $map = new PageType_interwikimap($pagetext);
196 return $this->_regexp;
199 function link($link, $linktext = false)
202 list ($moniker, $page) = explode(":", $link, 2);
204 if (!isset($this->_map[$moniker])) {
205 return HTML::span(array('class' => 'bad-interwiki'),
206 $linktext ? $linktext : $link);
209 $url = $this->_map[$moniker];
210 // localize Upload:links for WIKIDUMP
211 if (!empty($WikiTheme->DUMP_MODE) and $moniker == 'Upload') {
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('');
220 $ldir = strtolower($ldir);
221 $doc_root = strtolower($doc_root);
222 $wikiroot = strtolower($wikiroot);
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)));
231 // Urlencode page only if it's a query arg.
232 // FIXME: this is a somewhat broken heuristic.
233 if ($moniker == 'Upload') {
235 $page = rawurldecode($page);
237 $page_enc = strstr($url, '?') ? rawurlencode($page) : $page;
239 if (strstr($url, '%s'))
240 $url = sprintf($url, $page_enc);
244 $link = HTML::a(array('href' => $url));
247 $link->pushContent(PossiblyGlueIconToText('interwiki', "$moniker:"),
248 HTML::span(array('class' => 'wikipage'), $page));
249 $link->setAttr('class', 'interwiki');
251 $link->pushContent(PossiblyGlueIconToText('interwiki', $linktext));
252 $link->setAttr('class', 'named-interwiki');
259 function _parseMap($text)
261 if (!preg_match_all("/^\s*(\S+)\s+(.+)$/m",
262 $text, $matches, PREG_SET_ORDER)
266 foreach ($matches as $m) {
270 // Add virtual monikers: "Upload:" "Talk:" "User:", ":"
271 // and expand special variables %u, %b, %d
273 // Upload: Should be expanded later to user-specific upload dirs.
274 // In the Upload plugin, not here: Upload:ReiniUrban/uploaded-file.png
275 if (empty($map['Upload'])) {
276 $map['Upload'] = getUploadDataPath();
278 // User:ReiniUrban => ReiniUrban or Users/ReiniUrban
279 // Can be easily overriden by a customized InterWikiMap:
281 if (empty($map["User"])) {
284 // Talk:UserName => UserName/Discussion
285 // Talk:PageName => PageName/Discussion as default, which might be overridden
286 if (empty($map["Talk"])) {
287 $pagename = $GLOBALS['request']->getArg('pagename');
288 // against PageName/Discussion/Discussion
289 if (string_ends_with($pagename, SUBPAGE_SEPARATOR . _("Discussion")))
292 $map["Talk"] = "%s" . SUBPAGE_SEPARATOR . _("Discussion");
295 foreach (array('Upload', 'User', 'Talk') as $special) {
296 // Expand special variables:
299 // %d => iso8601 DateTime
300 // %s is expanded later to the pagename
301 if (strstr($map[$special], '%u'))
302 $map[$special] = str_replace($map[$special],
304 $GLOBALS['request']->_user->_userid);
305 if (strstr($map[$special], '%b'))
306 $map[$special] = str_replace($map[$special],
309 if (strstr($map[$special], '%d'))
310 $map[$special] = str_replace($map[$special],
312 // such as 2003-01-11T14:03:02+00:00
319 function _getMapFromWikiText($pagetext)
321 if (preg_match('|^<verbatim>\n(.*)^</verbatim>|ms', $pagetext, $m)) {
327 function _getMapFromFile($filename)
329 if (defined('WARN_NONPUBLIC_INTERWIKIMAP') and WARN_NONPUBLIC_INTERWIKIMAP) {
330 $error_html = sprintf(_("Loading InterWikiMap from external file %s."),
332 trigger_error($error_html, E_USER_NOTICE);
334 if (!file_exists($filename)) {
335 $finder = new FileFinder();
336 $filename = $finder->findFile(INTERWIKI_MAP_FILE);
338 @$fd = fopen($filename, "rb");
339 @$data = fread($fd, filesize($filename));
345 function _getRegexp()
348 return '(?:(?!a)a)'; // Never matches.
350 foreach (array_keys($this->_map) as $moniker)
351 $qkeys[] = preg_quote($moniker, '/');
352 return "(?:" . join("|", $qkeys) . ")";
357 /** How to transform text.
363 * @param WikiDB_Page $page
364 * @param hash $meta Version meta-data.
366 function PageFormatter(&$page, $meta)
368 $this->_page = $page;
369 $this->_meta = $meta;
370 if (!empty($meta['markup']))
371 $this->_markup = $meta['markup'];
373 $this->_markup = 2; // dump used old-markup as empty.
374 // FIXME: To be able to restore old plain-backups we should keep markup 1 as default.
375 // New policy: default = new markup (old crashes quite often)
378 function _transform($text)
380 include_once 'lib/BlockParser.php';
381 return TransformText($text, $this->_markup);
384 /** Transform the page text.
386 * @param string $text The raw page content (e.g. wiki-text).
387 * @return XmlContent Transformed content.
389 function format($text)
391 trigger_error("pure virtual", E_USER_ERROR);
395 class PageFormatter_wikitext extends PageFormatter
397 function format($text)
399 return HTML::div(array('class' => 'wikitext'),
400 $this->_transform($text));
404 class PageFormatter_interwikimap extends PageFormatter
406 function format($text)
408 return HTML::div(array('class' => 'wikitext'),
409 $this->_transform($this->_getHeader($text)),
410 $this->_formatMap($text),
411 $this->_transform($this->_getFooter($text)));
414 function _getHeader($text)
416 return preg_replace('/<verbatim>.*/s', '', $text);
419 function _getFooter($text)
421 return preg_replace('@.*?(</verbatim>|\Z)@s', '', $text, 1);
424 function _getMap($pagetext)
426 $map = getInterwikiMap($pagetext, 'force');
430 function _formatMap($pagetext)
432 $map = $this->_getMap($pagetext);
434 return HTML::p("<No interwiki map found>"); // Shouldn't happen.
436 $mon_attr = array('class' => 'interwiki-moniker');
437 $url_attr = array('class' => 'interwiki-url');
439 $thead = HTML::thead(HTML::tr(HTML::th($mon_attr, _("Moniker")),
440 HTML::th($url_attr, _("InterWiki Address"))));
441 foreach ($map as $moniker => $interurl) {
442 $rows[] = HTML::tr(HTML::td($mon_attr, new Cached_WikiLinkIfKnown($moniker)),
443 HTML::td($url_attr, HTML::tt($interurl)));
446 return HTML::table(array('class' => 'interwiki-map'),
448 HTML::tbody(false, $rows));
452 class FakePageRevision
454 function FakePageRevision($meta)
456 $this->_meta = $meta;
461 if (empty($this->_meta[$key]))
463 return $this->_meta[$key];
467 // abstract base class
468 class PageFormatter_attach extends PageFormatter
472 // Display templated contents for wikiblog, comment and wikiforum
473 function format($text)
475 if (empty($this->type))
476 trigger_error('PageFormatter_attach->format: $type missing');
477 include_once 'lib/Template.php';
479 $tokens['CONTENT'] = $this->_transform($text);
480 $tokens['page'] = $this->_page;
481 $tokens['rev'] = new FakePageRevision($this->_meta);
483 $name = new WikiPageName($this->_page->getName());
484 $tokens[$this->prefix . "_PARENT"] = $name->getParent();
486 $meta = $this->_meta[$this->type];
487 foreach (array('ctime', 'creator', 'creator_id') as $key)
488 $tokens[$this->prefix . "_" . strtoupper($key)] = $meta[$key];
490 return new Template($this->type, $request, $tokens);
494 class PageFormatter_wikiblog extends PageFormatter_attach
496 var $type = 'wikiblog', $prefix = "BLOG";
499 class PageFormatter_comment extends PageFormatter_attach
501 var $type = 'comment', $prefix = "COMMENT";
504 class PageFormatter_wikiforum extends PageFormatter_attach
506 var $type = 'wikiforum', $prefix = "FORUM";
509 /** wikiabuse for htmlarea editing. not yet used.
511 * Warning! Once a page is edited with a htmlarea like control it is
512 * stored in HTML and cannot be converted back to WikiText as long as
513 * we have no HTML => WikiText or any other interim format (WikiExchangeFormat e.g. XML)
514 * converter. See lib/HtmlParser.php for ongoing work on that.
515 * So it has a viral effect and certain plugins will not work anymore.
516 * But a lot of wikiusers seem to like it.
518 class PageFormatter_html extends PageFormatter
520 function _transform($text)
525 function format($text)
532 * FIXME. not yet used
534 class PageFormatter_pdf extends PageFormatter
537 function _transform($text)
539 include_once 'lib/BlockParser.php';
540 return TransformText($text, $this->_markup);
543 // one page or set of pages?
544 // here we try to format only a single page
545 function format($text)
547 include_once 'lib/Template.php';
549 $tokens['page'] = $this->_page;
550 $tokens['CONTENT'] = $this->_transform($text);
551 $pagename = $this->_page->getName();
553 // This is a XmlElement tree, which must be converted to PDF
555 // We can make use of several pdf extensions. This one - fpdf
556 // - is pure php and very easy, but looks quite ugly and has a
557 // terrible interface, as terrible as most of the othes.
558 // The closest to HTML is htmldoc which needs an external cgi
560 // We use a custom HTML->PDF class converter from PHPWebthings
561 // to be able to use templates for PDF.
562 require_once 'lib/fpdf.php';
563 require_once 'lib/pdf.php';
566 $pdf->SetTitle($pagename);
567 $pdf->SetAuthor($this->_page->get('author'));
568 $pdf->SetCreator(WikiURL($pagename, false, 1));
569 $pdf->AliasNbPages();
572 $pdf->SetFont('Times', '', 12);
573 //$pdf->SetFont('Arial','B',16);
575 // PDF pagelayout from a special template
576 $template = new Template('pdf', $request, $tokens);
577 $pdf->ConvertFromHTML($template);
579 // specify filename, destination
580 $pdf->Output($pagename . ".pdf", 'I'); // I for stdin or D for download
582 // Output([string name [, string dest]])
587 class PageFormatter_MediaWiki extends PageFormatter
589 function _transform($text)
591 include_once 'lib/BlockParser.php';
592 // Expand leading tabs.
593 $text = expand_tabs($text);
595 $input = new BlockParser_Input($text);
596 $output = $this->ParsedBlock($input);
597 return new XmlContent($output->getContent());
600 function format($text)
602 return HTML::div(array('class' => 'wikitext'),
603 $this->_transform($text));
611 // c-hanging-comment-ender-p: nil
612 // indent-tabs-mode: nil