2 rcs_id('$Id: PageType.php,v 1.53 2007-07-14 17:55:29 rurban Exp $');
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
19 along with PhpWiki; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 require_once('lib/CachedMarkup.php');
25 /** A cacheable formatted wiki page.
27 class TransformedText extends CacheableMarkup {
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.
36 function TransformedText($page, $text, $meta, $type_override=false) {
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),
53 * A page type descriptor.
55 * Encapsulate information about page types.
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
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.)
69 * Get a page type descriptor.
71 * This is a static member function.
73 * @param string $pagetype Name of the page type.
74 * @return PageType An object which is a subclass of PageType.
76 function GetPageType ($name=false) {
79 $class = "PageType_" . (string)$name;
80 if (class_exists($class))
82 trigger_error(sprintf("PageType '%s' unknown", (string)$name),
84 return new PageType_wikitext;
88 * Get the name of this page type.
90 * @return string Page type name.
93 if (!preg_match('/^PageType_(.+)$/i', get_class($this), $m))
94 trigger_error("Bad class name for formatter(?)", E_USER_ERROR);
99 * Transform page text.
101 * @param WikiDB_Page $page
102 * @param string $text
103 * @param hash $meta Version meta-data
104 * @return XmlContent The transformed page text.
106 function transform(&$page, &$text, $meta) {
107 $fmt_class = 'PageFormatter_' . $this->getName();
108 $formatter = new $fmt_class($page, $meta);
109 return $formatter->format($text);
113 class PageType_wikitext extends PageType {}
114 class PageType_html extends PageType {}
115 class PageType_pdf extends PageType {}
117 class PageType_wikiblog extends PageType {}
118 class PageType_comment extends PageType {}
119 class PageType_wikiforum extends PageType {}
121 class PageType_MediaWiki extends PageType {}
123 /* To prevent from PHP5 Fatal error: Using $this when not in object context */
124 function getInterwikiMap ($pagetext=false, $force=false) {
126 if (empty($map) or $force)
127 $map = new PageType_interwikimap($pagetext);
131 class PageType_interwikimap extends PageType
133 function PageType_interwikimap($pagetext = false) {
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."));
148 $intermap = $this->_getMapFromWikiText($pagetext);
150 if (!$intermap && defined('INTERWIKI_MAP_FILE'))
151 $intermap = $this->_getMapFromFile(INTERWIKI_MAP_FILE);
153 $this->_map = $this->_parseMap($intermap);
154 $this->_regexp = $this->_getRegexp();
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);
167 function getRegexp() {
168 return $this->_regexp;
171 function link ($link, $linktext = false) {
173 list ($moniker, $page) = split (":", $link, 2);
175 if (!isset($this->_map[$moniker])) {
176 return HTML::span(array('class' => 'bad-interwiki'),
177 $linktext ? $linktext : $link);
180 $url = $this->_map[$moniker];
181 // localize Upload:links for WIKIDUMP
182 if (!empty($WikiTheme->DUMP_MODE) and $moniker == 'Upload') {
184 $url = getUploadFilePath();
185 //calculate to a relative local path to /uploads for pdf images.
186 $doc_root = $request->get("DOCUMENT_ROOT");
187 $ldir = NormalizeLocalFileName($url);
188 $wikiroot = NormalizeLocalFileName('');
189 if (string_starts_with($ldir, $doc_root)) {
190 $link_prefix = substr($url, strlen($doc_root));
191 } elseif (string_starts_with($ldir, $wikiroot)) {
192 $link_prefix = NormalizeWebFileName(substr($url, strlen($wikiroot)));
196 // Urlencode page only if it's a query arg.
197 // FIXME: this is a somewhat broken heuristic.
198 $page_enc = strstr($url, '?') ? rawurlencode($page) : $page;
200 if (strstr($url, '%s'))
201 $url = sprintf($url, $page_enc);
205 $link = HTML::a(array('href' => $url));
208 $link->pushContent(PossiblyGlueIconToText('interwiki', "$moniker:"),
209 HTML::span(array('class' => 'wikipage'), $page));
210 $link->setAttr('class', 'interwiki');
213 $link->pushContent(PossiblyGlueIconToText('interwiki', $linktext));
214 $link->setAttr('class', 'named-interwiki');
221 function _parseMap ($text) {
222 if (!preg_match_all("/^\s*(\S+)\s+(.+)$/m",
223 $text, $matches, PREG_SET_ORDER))
226 foreach ($matches as $m) {
230 // Add virtual monikers: "Upload:" "Talk:" "User:", ":"
231 // and expand special variables %u, %b, %d
233 // Upload: Should be expanded later to user-specific upload dirs.
234 // In the Upload plugin, not here: Upload:ReiniUrban/uploaded-file.png
235 if (empty($map['Upload'])) {
236 $map['Upload'] = getUploadDataPath();
238 // User:ReiniUrban => ReiniUrban or Users/ReiniUrban
239 // Can be easily overriden by a customized InterWikiMap:
241 if (empty($map["User"])) {
244 // Talk:UserName => UserName/Discussion
245 // Talk:PageName => PageName/Discussion as default, which might be overridden
246 if (empty($map["Talk"])) {
247 $pagename = $GLOBALS['request']->getArg('pagename');
248 // against PageName/Discussion/Discussion
249 if (string_ends_with($pagename, SUBPAGE_SEPARATOR._("Discussion")))
252 $map["Talk"] = "%s".SUBPAGE_SEPARATOR._("Discussion");
255 foreach (array('Upload','User','Talk') as $special) {
256 // Expand special variables:
259 // %d => iso8601 DateTime
260 // %s is expanded later to the pagename
261 if (strstr($map[$special], '%u'))
262 $map[$special] = str_replace($map[$special],
264 $GLOBALS['request']->_user->_userid);
265 if (strstr($map[$special], '%b'))
266 $map[$special] = str_replace($map[$special],
269 if (strstr($map[$special], '%d'))
270 $map[$special] = str_replace($map[$special],
272 // such as 2003-01-11T14:03:02+00:00
279 function _getMapFromWikiText ($pagetext) {
280 if (preg_match('|^<verbatim>\n(.*)^</verbatim>|ms', $pagetext, $m)) {
286 function _getMapFromFile ($filename) {
287 if (defined('WARN_NONPUBLIC_INTERWIKIMAP') and WARN_NONPUBLIC_INTERWIKIMAP) {
288 $error_html = sprintf(_("Loading InterWikiMap from external file %s."),
290 trigger_error( $error_html, E_USER_NOTICE );
292 if (!file_exists($filename)) {
293 $finder = new FileFinder();
294 $filename = $finder->findFile(INTERWIKI_MAP_FILE);
296 @$fd = fopen ($filename, "rb");
297 @$data = fread ($fd, filesize($filename));
303 function _getRegexp () {
305 return '(?:(?!a)a)'; // Never matches.
307 foreach (array_keys($this->_map) as $moniker)
308 $qkeys[] = preg_quote($moniker, '/');
309 return "(?:" . join("|", $qkeys) . ")";
314 /** How to transform text.
316 class PageFormatter {
319 * @param WikiDB_Page $page
320 * @param hash $meta Version meta-data.
322 function PageFormatter(&$page, $meta) {
323 $this->_page = $page;
324 $this->_meta = $meta;
325 if (!empty($meta['markup']))
326 $this->_markup = $meta['markup'];
328 $this->_markup = 2; // dump used old-markup as empty.
329 // FIXME: To be able to restore old plain-backups we should keep markup 1 as default.
330 // New policy: default = new markup (old crashes quite often)
333 function _transform(&$text) {
334 include_once('lib/BlockParser.php');
335 return TransformText($text, $this->_markup);
338 /** Transform the page text.
340 * @param string $text The raw page content (e.g. wiki-text).
341 * @return XmlContent Transformed content.
343 function format($text) {
344 trigger_error("pure virtual", E_USER_ERROR);
348 class PageFormatter_wikitext extends PageFormatter
350 function format(&$text) {
351 return HTML::div(array('class' => 'wikitext'),
352 $this->_transform($text));
356 class PageFormatter_interwikimap extends PageFormatter
358 function format($text) {
359 return HTML::div(array('class' => 'wikitext'),
360 $this->_transform($this->_getHeader($text)),
361 $this->_formatMap($text),
362 $this->_transform($this->_getFooter($text)));
365 function _getHeader($text) {
366 return preg_replace('/<verbatim>.*/s', '', $text);
369 function _getFooter($text) {
370 return preg_replace('@.*?(</verbatim>|\Z)@s', '', $text, 1);
373 function _getMap($pagetext) {
374 $map = getInterwikiMap($pagetext, 'force');
378 function _formatMap($pagetext) {
379 $map = $this->_getMap($pagetext);
381 return HTML::p("<No interwiki map found>"); // Shouldn't happen.
383 $mon_attr = array('class' => 'interwiki-moniker');
384 $url_attr = array('class' => 'interwiki-url');
386 $thead = HTML::thead(HTML::tr(HTML::th($mon_attr, _("Moniker")),
387 HTML::th($url_attr, _("InterWiki Address"))));
388 foreach ($map as $moniker => $interurl) {
389 $rows[] = HTML::tr(HTML::td($mon_attr, new Cached_WikiLinkIfKnown($moniker)),
390 HTML::td($url_attr, HTML::tt($interurl)));
393 return HTML::table(array('class' => 'interwiki-map'),
395 HTML::tbody(false, $rows));
399 class FakePageRevision {
400 function FakePageRevision($meta) {
401 $this->_meta = $meta;
405 if (empty($this->_meta[$key]))
407 return $this->_meta[$key];
411 // abstract base class
412 class PageFormatter_attach extends PageFormatter
416 // Display templated contents for wikiblog, comment and wikiforum
417 function format($text) {
418 if (empty($this->type))
419 trigger_error('PageFormatter_attach->format: $type missing');
420 include_once('lib/Template.php');
422 $tokens['CONTENT'] = $this->_transform($text);
423 $tokens['page'] = $this->_page;
424 $tokens['rev'] = new FakePageRevision($this->_meta);
426 $name = new WikiPageName($this->_page->getName());
427 $tokens[$this->prefix."_PARENT"] = $name->getParent();
429 $meta = $this->_meta[$this->type];
430 foreach(array('ctime', 'creator', 'creator_id') as $key)
431 $tokens[$this->prefix . "_" . strtoupper($key)] = $meta[$key];
433 return new Template($this->type, $request, $tokens);
437 class PageFormatter_wikiblog extends PageFormatter_attach {
438 var $type = 'wikiblog', $prefix = "BLOG";
440 class PageFormatter_comment extends PageFormatter_attach {
441 var $type = 'comment', $prefix = "COMMENT";
443 class PageFormatter_wikiforum extends PageFormatter_attach {
444 var $type = 'wikiforum', $prefix = "FORUM";
447 /** wikiabuse for htmlarea editing. not yet used.
449 * Warning! Once a page is edited with a htmlarea like control it is
450 * stored in HTML and cannot be converted back to WikiText as long as
451 * we have no HTML => WikiText or any other interim format (WikiExchangeFormat e.g. XML)
452 * converter. See lib/HtmlParser.php for ongoing work on that.
453 * So it has a viral effect and certain plugins will not work anymore.
454 * But a lot of wikiusers seem to like it.
456 class PageFormatter_html extends PageFormatter
458 function _transform($text) {
461 function format($text) {
467 * FIXME. not yet used
469 class PageFormatter_pdf extends PageFormatter
472 function _transform($text) {
473 include_once('lib/BlockParser.php');
474 return TransformText($text, $this->_markup);
477 // one page or set of pages?
478 // here we try to format only a single page
479 function format($text) {
480 include_once('lib/Template.php');
482 $tokens['page'] = $this->_page;
483 $tokens['CONTENT'] = $this->_transform($text);
484 $pagename = $this->_page->getName();
486 // This is a XmlElement tree, which must be converted to PDF
488 // We can make use of several pdf extensions. This one - fpdf
489 // - is pure php and very easy, but looks quite ugly and has a
490 // terrible interface, as terrible as most of the othes.
491 // The closest to HTML is htmldoc which needs an external cgi
493 // We use a custom HTML->PDF class converter from PHPWebthings
494 // to be able to use templates for PDF.
495 require_once('lib/fpdf.php');
496 require_once('lib/pdf.php');
499 $pdf->SetTitle($pagename);
500 $pdf->SetAuthor($this->_page->get('author'));
501 $pdf->SetCreator(WikiURL($pagename,false,1));
502 $pdf->AliasNbPages();
505 $pdf->SetFont('Times','',12);
506 //$pdf->SetFont('Arial','B',16);
508 // PDF pagelayout from a special template
509 $template = new Template('pdf', $request, $tokens);
510 $pdf->ConvertFromHTML($template);
512 // specify filename, destination
513 $pdf->Output($pagename.".pdf",'I'); // I for stdin or D for download
515 // Output([string name [, string dest]])
520 class PageFormatter_MediaWiki extends PageFormatter
522 function _transform(&$text) {
523 include_once('lib/BlockParser.php');
524 // Expand leading tabs.
525 $text = expand_tabs($text);
527 $input = new BlockParser_Input($text);
528 $output = $this->ParsedBlock($input);
529 return new XmlContent($output->getContent());
532 function format(&$text) {
533 return HTML::div(array('class' => 'wikitext'),
534 $this->_transform($text));
539 // $Log: not supported by cvs2svn $
540 // Revision 1.52 2007/02/17 14:17:41 rurban
541 // localize Upload:links for WIKIDUMP: esp. for pdf images
543 // Revision 1.51 2007/01/07 18:43:17 rurban
544 // Disallow ":" as interwikmap and use it as proper LinkedBracket match.
546 // Revision 1.50 2007/01/04 16:44:57 rurban
547 // Force interwiki updates and page edits
549 // Revision 1.49 2006/10/12 06:25:09 rurban
550 // use the same class for $moniker == ""
552 // Revision 1.48 2006/10/08 12:38:11 rurban
553 // New special interwiki link markup [:LinkTo] without storing the backlink
555 // Revision 1.47 2005/08/07 09:14:38 rurban
558 // Revision 1.46 2005/08/06 13:09:33 rurban
559 // allow spaces in interwiki paths, even implicitly. fixes bug #1218733
561 // Revision 1.45 2005/05/06 16:48:41 rurban
562 // support %u, %b, %d expansion for Upload: User: and Talk: interwiki monikers
564 // Revision 1.44 2005/04/23 11:07:34 rurban
567 // Revision 1.43 2005/02/02 20:40:12 rurban
568 // fix Talk: and User: names and links
570 // Revision 1.42 2005/02/02 19:36:56 rurban
573 // Revision 1.41 2005/02/02 19:34:09 rurban
574 // more maps: Talk, User
576 // Revision 1.40 2005/01/31 12:15:08 rurban
577 // avoid some cornercase intermap warning. Thanks to Stefan <sonstiges@bayern-mail.de>
579 // Revision 1.39 2005/01/25 06:59:35 rurban
580 // fix bogus InterWikiMap warning
582 // Revision 1.38 2004/12/26 17:10:44 rurban
583 // just docs or whitespace
585 // Revision 1.37 2004/12/06 19:49:55 rurban
586 // enable action=remove which is undoable and seeable in RecentChanges: ADODB ony for now.
587 // renamed delete_page to purge_page.
588 // enable action=edit&version=-1 to force creation of a new version.
589 // added BABYCART_PATH config
590 // fixed magiqc in adodb.inc.php
591 // and some more docs
598 // c-hanging-comment-ender-p: nil
599 // indent-tabs-mode: nil