]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/PageType.php
Force interwiki updates and page edits
[SourceForge/phpwiki.git] / lib / PageType.php
1 <?php // -*-php-*-
2 rcs_id('$Id: PageType.php,v 1.50 2007-01-04 16:44:57 rurban Exp $');
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
19  along with PhpWiki; if not, write to the Free Software
20  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22
23 require_once('lib/CachedMarkup.php');
24
25 /** A cacheable formatted wiki page.
26  */
27 class TransformedText extends CacheableMarkup {
28     /** Constructor.
29      *
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.
35      */
36     function TransformedText($page, $text, $meta, $type_override=false) {
37         $pagetype = false;
38         if ($type_override)
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),
44                                $page->getName());
45     }
46
47     function getType() {
48         return $this->_type;
49     }
50 }
51
52 /**
53  * A page type descriptor.
54  *
55  * Encapsulate information about page types.
56  *
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 
61  * pure HTML.
62  *
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.)
66  */
67 class PageType {
68     /**
69      * Get a page type descriptor.
70      *
71      * This is a static member function.
72      *
73      * @param string $pagetype  Name of the page type.
74      * @return PageType  An object which is a subclass of PageType.
75      */
76     function GetPageType ($name=false) {
77         if (!$name)
78             $name = 'wikitext';
79         $class = "PageType_" . (string)$name;
80         if (class_exists($class))
81             return new $class;
82         trigger_error(sprintf("PageType '%s' unknown", (string)$name),
83                       E_USER_WARNING);
84         return new PageType_wikitext;
85     }
86
87     /**
88      * Get the name of this page type.
89      *
90      * @return string  Page type name.
91      */
92     function getName() {
93         if (!preg_match('/^PageType_(.+)$/i', get_class($this), $m))
94             trigger_error("Bad class name for formatter(?)", E_USER_ERROR);
95         return $m[1];
96     }
97
98     /**
99      * Transform page text.
100      *
101      * @param WikiDB_Page $page
102      * @param string $text
103      * @param hash $meta Version meta-data
104      * @return XmlContent The transformed page text.
105      */
106     function transform(&$page, &$text, $meta) {
107         $fmt_class = 'PageFormatter_' . $this->getName();
108         $formatter = new $fmt_class($page, $meta);
109         return $formatter->format($text);
110     }
111 }
112
113 class PageType_wikitext extends PageType {}
114 class PageType_html extends PageType {}
115 class PageType_pdf extends PageType {}
116
117 class PageType_wikiblog extends PageType {}
118 class PageType_comment extends PageType {}
119 class PageType_wikiforum extends PageType {}
120
121 /* To prevent from PHP5 Fatal error: Using $this when not in object context */
122 function getInterwikiMap ($pagetext=false, $force=false) {
123     static $map;
124     if (empty($map) or $force)
125         $map = new PageType_interwikimap($pagetext);
126     return $map;
127 }
128
129 class PageType_interwikimap extends PageType
130 {
131     function PageType_interwikimap($pagetext = false) {
132         if (!$pagetext) {
133             $dbi = $GLOBALS['request']->getDbh();
134             $page = $dbi->getPage(_("InterWikiMap"));
135             if ($page->get('locked')) {
136                 $current = $page->getCurrentRevision();
137                 $pagetext = $current->getPackedContent();
138                 $intermap = $this->_getMapFromWikiText($pagetext);
139             } elseif ($page->exists()) {
140                 trigger_error(_("WARNING: InterWikiMap page is unlocked, so not using those links."));
141                 $intermap = false;
142             }
143             else 
144                 $intermap = false;
145         } else {
146             $intermap = $this->_getMapFromWikiText($pagetext);
147         }
148         if (!$intermap && defined('INTERWIKI_MAP_FILE'))
149             $intermap = $this->_getMapFromFile(INTERWIKI_MAP_FILE);
150
151         $this->_map = $this->_parseMap($intermap);
152         $this->_regexp = $this->_getRegexp();
153     }
154
155     function GetMap ($pagetext = false) {
156         /*PHP5 Fatal error: Using $this when not in object context */
157         if (empty($this->_map)) {
158             $map = new PageType_interwikimap($pagetext);
159             return $map;
160         } else {
161             return $this;
162         }
163     }
164
165     function getRegexp() {
166         return $this->_regexp;
167     }
168
169     function link ($link, $linktext = false) {
170         list ($moniker, $page) = split (":", $link, 2);
171         
172         if (!isset($this->_map[$moniker])) {
173             return HTML::span(array('class' => 'bad-interwiki'),
174                               $linktext ? $linktext : $link);
175         }
176
177         $url = $this->_map[$moniker];
178         
179         // Urlencode page only if it's a query arg.
180         // FIXME: this is a somewhat broken heuristic.
181         $page_enc = strstr($url, '?') ? rawurlencode($page) : $page;
182
183         if (strstr($url, '%s'))
184             $url = sprintf($url, $page_enc);
185         else
186             $url .= $page_enc;
187
188         $link = HTML::a(array('href' => $url));
189
190         if (!$linktext) {
191             $link->pushContent(PossiblyGlueIconToText('interwiki', "$moniker:"),
192                                HTML::span(array('class' => 'wikipage'), $page));
193             $link->setAttr('class', 'interwiki');
194         }
195         else {
196             $link->pushContent(PossiblyGlueIconToText('interwiki', $linktext));
197             $link->setAttr('class', 'named-interwiki');
198         }
199         
200         return $link;
201     }
202
203
204     function _parseMap ($text) {
205         if (!preg_match_all("/^\s*(\S+)\s+(.+)$/m",
206                             $text, $matches, PREG_SET_ORDER))
207             return false;
208
209         foreach ($matches as $m) {
210             $map[$m[1]] = $m[2];
211         }
212
213         // Add virtual monikers: "Upload:" "Talk:" "User:", ":"
214         // and expand special variables %u, %b, %d
215
216         // Upload: Should be expanded later to user-specific upload dirs. 
217         // In the Upload plugin, not here: Upload:ReiniUrban/uploaded-file.png
218         if (empty($map['Upload'])) {
219             $map['Upload'] = getUploadDataPath();
220         }
221         // User:ReiniUrban => ReiniUrban or Users/ReiniUrban
222         // Can be easily overriden by a customized InterWikiMap: 
223         //   User Users/%s
224         if (empty($map["User"])) {
225             $map["User"] = "%s";
226         }
227         // Talk:UserName => UserName/Discussion
228         // Talk:PageName => PageName/Discussion as default, which might be overridden
229         if (empty($map["Talk"])) {
230             $pagename = $GLOBALS['request']->getArg('pagename');
231             // against PageName/Discussion/Discussion
232             if (string_ends_with($pagename, SUBPAGE_SEPARATOR._("Discussion")))
233                 $map["Talk"] = "%s";
234             else
235                 $map["Talk"] = "%s".SUBPAGE_SEPARATOR._("Discussion");
236         }
237         if (empty($map[''])) { // Magic syntax: Don't store links ":PageName" as backlink
238             $map[''] = '%s';
239         }
240
241         foreach (array('Upload','User','Talk') as $special) {
242             // Expand special variables:
243             //   %u => username
244             //   %b => wikibaseurl
245             //   %d => iso8601 DateTime
246             // %s is expanded later to the pagename
247             if (strstr($map[$special], '%u'))
248                 $map[$special] = str_replace($map[$special],
249                                              '%u', 
250                                              $GLOBALS['request']->_user->_userid);
251             if (strstr($map[$special], '%b'))
252                 $map[$special] = str_replace($map[$special],
253                                              '%b', 
254                                              PHPWIKI_BASE_URL);
255             if (strstr($map[$special], '%d'))
256                 $map[$special] = str_replace($map[$special],
257                                              '%d', 
258                                              // such as 2003-01-11T14:03:02+00:00
259                                              Iso8601DateTime());
260         }
261
262         // Maybe add other monikers also - SemanticWeb link predicates
263         // Should they be defined in a RDF? (strict mode)
264         // Or should the SemanticWeb lib add it by itself? 
265         // (adding only a subset dependent on the context = model)
266         return $map;
267     }
268
269     function _getMapFromWikiText ($pagetext) {
270         if (preg_match('|^<verbatim>\n(.*)^</verbatim>|ms', $pagetext, $m)) {
271             return $m[1];
272         }
273         return false;
274     }
275
276     function _getMapFromFile ($filename) {
277         if (defined('WARN_NONPUBLIC_INTERWIKIMAP') and WARN_NONPUBLIC_INTERWIKIMAP) {
278             $error_html = sprintf(_("Loading InterWikiMap from external file %s."), 
279                                   $filename);
280             trigger_error( $error_html, E_USER_NOTICE );
281         }
282         if (!file_exists($filename)) {
283             $finder = new FileFinder();
284             $filename = $finder->findFile(INTERWIKI_MAP_FILE);
285         }
286         @$fd = fopen ($filename, "rb");
287         @$data = fread ($fd, filesize($filename));
288         @fclose ($fd);
289
290         return $data;
291     }
292
293     function _getRegexp () {
294         if (!$this->_map)
295             return '(?:(?!a)a)'; //  Never matches.
296         
297         foreach (array_keys($this->_map) as $moniker)
298             $qkeys[] = preg_quote($moniker, '/');
299         return "(?:" . join("|", $qkeys) . ")";
300     }
301 }
302
303
304 /** How to transform text.
305  */
306 class PageFormatter {
307     /** Constructor.
308      *
309      * @param WikiDB_Page $page
310      * @param hash $meta Version meta-data.
311      */
312     function PageFormatter(&$page, $meta) {
313         $this->_page = $page;
314         $this->_meta = $meta;
315         if (!empty($meta['markup']))
316             $this->_markup = $meta['markup'];
317         else
318             $this->_markup = 2; // dump used old-markup as empty. 
319         // FIXME: To be able to restore old plain-backups we should keep markup 1 as default.
320         // New policy: default = new markup (old crashes quite often)
321     }
322
323     function _transform(&$text) {
324         include_once('lib/BlockParser.php');
325         return TransformText($text, $this->_markup);
326     }
327
328     /** Transform the page text.
329      *
330      * @param string $text  The raw page content (e.g. wiki-text).
331      * @return XmlContent   Transformed content.
332      */
333     function format($text) {
334         trigger_error("pure virtual", E_USER_ERROR);
335     }
336 }
337
338 class PageFormatter_wikitext extends PageFormatter 
339 {
340     function format(&$text) {
341         return HTML::div(array('class' => 'wikitext'),
342                          $this->_transform($text));
343     }
344 }
345
346 class PageFormatter_interwikimap extends PageFormatter
347 {
348     function format($text) {
349         return HTML::div(array('class' => 'wikitext'),
350                          $this->_transform($this->_getHeader($text)),
351                          $this->_formatMap($text),
352                          $this->_transform($this->_getFooter($text)));
353     }
354
355     function _getHeader($text) {
356         return preg_replace('/<verbatim>.*/s', '', $text);
357     }
358
359     function _getFooter($text) {
360         return preg_replace('@.*?(</verbatim>|\Z)@s', '', $text, 1);
361     }
362     
363     function _getMap($pagetext) {
364         $map = getInterwikiMap($pagetext, 'force');
365         return $map->_map;
366     }
367     
368     function _formatMap($pagetext) {
369         $map = $this->_getMap($pagetext);
370         if (!$map)
371             return HTML::p("<No interwiki map found>"); // Shouldn't happen.
372
373         $mon_attr = array('class' => 'interwiki-moniker');
374         $url_attr = array('class' => 'interwiki-url');
375         
376         $thead = HTML::thead(HTML::tr(HTML::th($mon_attr, _("Moniker")),
377                                       HTML::th($url_attr, _("InterWiki Address"))));
378         foreach ($map as $moniker => $interurl) {
379             $rows[] = HTML::tr(HTML::td($mon_attr, new Cached_WikiLinkIfKnown($moniker)),
380                                HTML::td($url_attr, HTML::tt($interurl)));
381         }
382         
383         return HTML::table(array('class' => 'interwiki-map'),
384                            $thead,
385                            HTML::tbody(false, $rows));
386     }
387 }
388
389 class FakePageRevision {
390     function FakePageRevision($meta) {
391         $this->_meta = $meta;
392     }
393
394     function get($key) {
395         if (empty($this->_meta[$key]))
396             return false;
397         return $this->_meta[$key];
398     }
399 }
400
401 // abstract base class
402 class PageFormatter_attach extends PageFormatter
403 {
404     var $type, $prefix;
405     
406     // Display templated contents for wikiblog, comment and wikiforum
407     function format($text) {
408         if (empty($this->type))
409             trigger_error('PageFormatter_attach->format: $type missing');
410         include_once('lib/Template.php');
411         global $request;
412         $tokens['CONTENT'] = $this->_transform($text);
413         $tokens['page'] = $this->_page;
414         $tokens['rev'] = new FakePageRevision($this->_meta);
415
416         $name = new WikiPageName($this->_page->getName());
417         $tokens[$this->prefix."_PARENT"] = $name->getParent();
418
419         $meta = $this->_meta[$this->type];
420         foreach(array('ctime', 'creator', 'creator_id') as $key)
421             $tokens[$this->prefix . "_" . strtoupper($key)] = $meta[$key];
422         
423         return new Template($this->type, $request, $tokens);
424     }
425 }
426
427 class PageFormatter_wikiblog extends PageFormatter_attach {
428     var $type = 'wikiblog', $prefix = "BLOG";
429 }
430 class PageFormatter_comment extends PageFormatter_attach {
431     var $type = 'comment', $prefix = "COMMENT";
432 }
433 class PageFormatter_wikiforum extends PageFormatter_attach {
434     var $type = 'wikiforum', $prefix = "FORUM";
435 }
436
437 /** wikiabuse for htmlarea editing. not yet used.  
438  *
439  * Warning! Once a page is edited with a htmlarea like control it is
440  * stored in HTML and cannot be converted back to WikiText as long as
441  * we have no HTML => WikiText or any other interim format (WikiExchangeFormat e.g. XML) 
442  * converter. See lib/HtmlParser.php for ongoing work on that. 
443  * So it has a viral effect and certain plugins will not work anymore.
444  * But a lot of wikiusers seem to like it.
445  */
446 class PageFormatter_html extends PageFormatter
447 {
448     function _transform($text) {
449         return $text;
450     }
451     function format($text) {
452         return $text;
453     }
454 }
455
456 /**
457  *  FIXME. not yet used
458  */
459 class PageFormatter_pdf extends PageFormatter
460 {
461
462     function _transform($text) {
463         include_once('lib/BlockParser.php');
464         return TransformText($text, $this->_markup);
465     }
466
467     // one page or set of pages?
468     // here we try to format only a single page
469     function format($text) {
470         include_once('lib/Template.php');
471         global $request;
472         $tokens['page']    = $this->_page;
473         $tokens['CONTENT'] = $this->_transform($text);
474         $pagename = $this->_page->getName();
475
476         // This is a XmlElement tree, which must be converted to PDF
477
478         // We can make use of several pdf extensions. This one - fpdf
479         // - is pure php and very easy, but looks quite ugly and has a
480         // terrible interface, as terrible as most of the othes. 
481         // The closest to HTML is htmldoc which needs an external cgi
482         // binary.
483         // We use a custom HTML->PDF class converter from PHPWebthings
484         // to be able to use templates for PDF.
485         require_once('lib/fpdf.php');
486         require_once('lib/pdf.php');
487
488         $pdf = new PDF();
489         $pdf->SetTitle($pagename);
490         $pdf->SetAuthor($this->_page->get('author'));
491         $pdf->SetCreator(WikiURL($pagename,false,1));
492         $pdf->AliasNbPages();
493         $pdf->AddPage();
494         //TODO: define fonts
495         $pdf->SetFont('Times','',12);
496         //$pdf->SetFont('Arial','B',16);
497
498         // PDF pagelayout from a special template
499         $template = new Template('pdf', $request, $tokens);
500         $pdf->ConvertFromHTML($template);
501
502         // specify filename, destination
503         $pdf->Output($pagename.".pdf",'I'); // I for stdin or D for download
504
505         // Output([string name [, string dest]])
506         return $pdf;
507     }
508 }
509
510 // $Log: not supported by cvs2svn $
511 // Revision 1.49  2006/10/12 06:25:09  rurban
512 // use the same class for $moniker == ""
513 //
514 // Revision 1.48  2006/10/08 12:38:11  rurban
515 // New special interwiki link markup [:LinkTo] without storing the backlink
516 //
517 // Revision 1.47  2005/08/07 09:14:38  rurban
518 // fix comments
519 //
520 // Revision 1.46  2005/08/06 13:09:33  rurban
521 // allow spaces in interwiki paths, even implicitly. fixes bug #1218733
522 //
523 // Revision 1.45  2005/05/06 16:48:41  rurban
524 // support %u, %b, %d expansion for Upload: User: and Talk: interwiki monikers
525 //
526 // Revision 1.44  2005/04/23 11:07:34  rurban
527 // cache map
528 //
529 // Revision 1.43  2005/02/02 20:40:12  rurban
530 // fix Talk: and User: names and links
531 //
532 // Revision 1.42  2005/02/02 19:36:56  rurban
533 // more plans
534 //
535 // Revision 1.41  2005/02/02 19:34:09  rurban
536 // more maps: Talk, User
537 //
538 // Revision 1.40  2005/01/31 12:15:08  rurban
539 // avoid some cornercase intermap warning. Thanks to Stefan <sonstiges@bayern-mail.de>
540 //
541 // Revision 1.39  2005/01/25 06:59:35  rurban
542 // fix bogus InterWikiMap warning
543 //
544 // Revision 1.38  2004/12/26 17:10:44  rurban
545 // just docs or whitespace
546 //
547 // Revision 1.37  2004/12/06 19:49:55  rurban
548 // enable action=remove which is undoable and seeable in RecentChanges: ADODB ony for now.
549 // renamed delete_page to purge_page.
550 // enable action=edit&version=-1 to force creation of a new version.
551 // added BABYCART_PATH config
552 // fixed magiqc in adodb.inc.php
553 // and some more docs
554 //
555
556 // Local Variables:
557 // mode: php
558 // tab-width: 8
559 // c-basic-offset: 4
560 // c-hanging-comment-ender-p: nil
561 // indent-tabs-mode: nil
562 // End:
563 ?>