]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/stdlib.php
Fix for invalid HTML from doubly-indented list elements.
[SourceForge/phpwiki.git] / lib / stdlib.php
1 <?php rcs_id('$Id: stdlib.php,v 1.21.2.3 2001-03-02 21:26:03 dairiki Exp $');
2
3    /*
4       Standard functions for Wiki functionality
5          ExitWiki($errormsg)
6          LinkExistingWikiWord($wikiword, $linktext) 
7          LinkUnknownWikiWord($wikiword, $linktext) 
8          LinkURL($url, $linktext)
9          LinkImage($url, $alt)
10          RenderQuickSearch($value)
11          RenderFullSearch($value)
12          RenderMostPopular()
13          CookSpaces($pagearray) 
14          class Stack (push(), pop(), cnt(), top())
15          SetHTMLOutputMode($newmode, $depth)
16          UpdateRecentChanges($dbi, $pagename, $isnewpage) 
17          ParseAndLink($bracketlink)
18          ExtractWikiPageLinks($content)
19          LinkRelatedPages($dbi, $pagename)
20          GeneratePage($template, $content, $name, $hash)
21    */
22
23
24    function ExitWiki($errormsg)
25    {
26       static $exitwiki = 0;
27       global $dbi;
28
29       if($exitwiki)             // just in case CloseDataBase calls us
30          exit();
31       $exitwiki = 1;
32
33       CloseDataBase($dbi);
34
35       if($errormsg <> '') {
36          print "<P><hr noshade><h2>" . gettext("WikiFatalError") . "</h2>\n";
37          print $errormsg;
38          print "\n</BODY></HTML>";
39       }
40       exit;
41    }
42
43
44    function LinkExistingWikiWord($wikiword, $linktext='') {
45       global $ScriptUrl;
46       $enc_word = rawurlencode($wikiword);
47       if(empty($linktext))
48          $linktext = htmlspecialchars($wikiword);
49       return "<a href=\"$ScriptUrl?$enc_word\">$linktext</a>";
50    }
51
52    function LinkUnknownWikiWord($wikiword, $linktext='') {
53       global $ScriptUrl;
54       $enc_word = rawurlencode($wikiword);
55       if(empty($linktext))
56          $linktext = htmlspecialchars($wikiword);
57       return "<u>$linktext</u><a href=\"$ScriptUrl?edit=$enc_word\">?</a>";
58    }
59
60    function LinkURL($url, $linktext='') {
61       global $ScriptUrl;
62       if(ereg("[<>\"]", $url)) {
63          return "<b><u>BAD URL -- remove all of &lt;, &gt;, &quot;</u></b>";
64       }
65       if(empty($linktext))
66          $linktext = htmlspecialchars($url);
67       return "<a href=\"$url\">$linktext</a>";
68    }
69
70    function LinkImage($url, $alt='[External Image]') {
71       global $ScriptUrl;
72       if(ereg('[<>"]', $url)) {
73          return "<b><u>BAD URL -- remove all of &lt;, &gt;, &quot;</u></b>";
74       }
75       return "<img src=\"$url\" ALT=\"$alt\">";
76    }
77
78    
79    function RenderQuickSearch($value = '') {
80       global $ScriptUrl;
81       return "<form action=\"$ScriptUrl\">\n" .
82              "<input type=text size=30 name=search value=\"$value\">\n" .
83              "<input type=submit value=\"". gettext("Search") .
84              "\"></form>\n";
85    }
86
87    function RenderFullSearch($value = '') {
88       global $ScriptUrl;
89       return "<form action=\"$ScriptUrl\">\n" .
90              "<input type=text size=30 name=full value=\"$value\">\n" .
91              "<input type=submit value=\"". gettext("Search") .
92              "\"></form>\n";
93    }
94
95    function RenderMostPopular() {
96       global $ScriptUrl, $dbi;
97       
98       $query = InitMostPopular($dbi, MOST_POPULAR_LIST_LENGTH);
99       $result = "<DL>\n";
100       while ($qhash = MostPopularNextMatch($dbi, $query)) {
101          $result .= "<DD>$qhash[hits] ... " . LinkExistingWikiWord($qhash['pagename']) . "\n";
102       }
103       $result .= "</DL>\n";
104       
105       return $result;
106    }
107
108
109    function ParseAdminTokens($line) {
110       global $ScriptUrl;
111       
112       while (preg_match("/%%ADMIN-INPUT-(.*?)-(\w+)%%/", $line, $matches)) {
113          $head = str_replace('_', ' ', $matches[2]);
114          $form = "<FORM ACTION=\"$ScriptUrl\" METHOD=POST>"
115                 ."$head: <INPUT NAME=$matches[1] SIZE=20> "
116                 ."<INPUT TYPE=SUBMIT VALUE=\"" . gettext("Go") . "\">"
117                 ."</FORM>";
118          $line = str_replace($matches[0], $form, $line);
119       }
120       return $line;
121    }
122
123    // converts spaces to tabs
124    function CookSpaces($pagearray) {
125       return preg_replace("/ {3,8}/", "\t", $pagearray);
126    }
127
128
129    class Stack {
130       var $items = array();
131       var $size = 0;
132
133       function push($item) {
134          $this->items[$this->size] = $item;
135          $this->size++;
136          return true;
137       }  
138    
139       function pop() {
140          if ($this->size == 0) {
141             return false; // stack is empty
142          }  
143          $this->size--;
144          return $this->items[$this->size];
145       }  
146    
147       function cnt() {
148          return $this->size;
149       }  
150
151       function top() {
152          if($this->size)
153             return $this->items[$this->size - 1];
154          else
155             return '';
156       }  
157
158    }  
159    // end class definition
160
161
162    // I couldn't move this to lib/config.php because it wasn't declared yet.
163    $stack = new Stack;
164
165    /* 
166       Wiki HTML output can, at any given time, be in only one mode.
167       It will be something like Unordered List, Preformatted Text,
168       plain text etc. When we change modes we have to issue close tags
169       for one mode and start tags for another.
170
171       $tag ... HTML tag to insert
172       $tagtype ... ZERO_LEVEL - close all open tags before inserting $tag
173                    NESTED_LEVEL - close tags until depths match
174       $level ... nesting level (depth) of $tag
175                  nesting is arbitrary limited to 10 levels
176    */
177
178    function SetHTMLOutputMode($tag, $tagtype, $level)
179    {
180       global $stack;
181       $retvar = '';
182
183       if ($level > 10) {
184           // arbitrarily limit tag nesting
185           ExitWiki(gettext ("Nesting depth exceeded in SetHTMLOutputMode"));
186       }
187       
188       if ($tagtype == ZERO_LEVEL) {
189          // empty the stack until $level == 0;
190          if ($tag == $stack->top()) {
191             return; // same tag? -> nothing to do
192          }
193          while ($stack->cnt() > 0) {
194             $closetag = $stack->pop();
195             $retvar .= "</$closetag>\n";
196          }
197    
198          if ($tag) {
199             $retvar .= "<$tag>\n";
200             $stack->push($tag);
201          }
202
203
204       } elseif ($tagtype == NESTED_LEVEL) {
205          if ($level < $stack->cnt()) {
206             // $tag has fewer nestings (old: tabs) than stack,
207             // reduce stack to that tab count
208             while ($stack->cnt() > $level) {
209                $closetag = $stack->pop();
210                if ($closetag == false) {
211                   //echo "bounds error in tag stack";
212                   break;
213                }
214                $retvar .= "</$closetag>\n";
215             }
216
217             // if list type isn't the same,
218             // back up one more and push new tag
219             if ($tag != $stack->top()) {
220                $closetag = $stack->pop();
221                $retvar .= "</$closetag><$tag>\n";
222                $stack->push($tag);
223             }
224    
225          } elseif ($level > $stack->cnt()) {
226             // Test for and close top level elements which are not allowed to contain
227             // other block-level elements.
228             if ($stack->cnt() == 1 and
229                 preg_match('/^(p|pre|h\d)$/i', $stack->top()))
230             {
231                $closetag = $stack->pop();
232                $retvar .= "</$closetag>";
233             }
234
235             // we add the diff to the stack
236             // stack might be zero
237             if ($stack->cnt() < $level) {
238                while ($stack->cnt() < $level - 1) {
239                   // This is a bit of a hack:
240                   //
241                   // We're not nested deep enough, and have to make up
242                   // some kind of block element to nest within.
243                   //
244                   // Currently, this can only happen for nested list
245                   // element (either <ul> <ol> or <dl>).  What we used
246                   // to do here is to open extra lists of whatever
247                   // type was requested.  This would result in invalid
248                   // HTML, since and list is not allowed to contain
249                   // another list without first containing a list
250                   // item.  ("<ul><ul><li>Item</ul></ul>" is invalid.)
251                   //
252                   // So now, when we need extra list elements, we use
253                   // a <dl>, and open it with an empty <dd>.
254
255                   $retvar .= "<dl><dd>";
256                   $stack->push('dl');
257                }
258
259                $retvar .= "<$tag>\n";
260                $stack->push($tag);
261             }
262    
263          } else { // $level == $stack->cnt()
264             if ($tag == $stack->top()) {
265                return; // same tag? -> nothing to do
266             } else {
267                // different tag - close old one, add new one
268                $closetag = $stack->pop();
269                $retvar .= "</$closetag>\n";
270                $retvar .= "<$tag>\n";
271                $stack->push($tag);
272             }
273          }
274
275    
276       } else { // unknown $tagtype
277          ExitWiki ("Passed bad tag type value in SetHTMLOutputMode");
278       }
279
280       return $retvar;
281    }
282    // end SetHTMLOutputMode
283
284
285
286    function ParseAndLink($bracketlink) {
287       global $dbi, $ScriptUrl, $AllowedProtocols, $InlineImages;
288
289       // $bracketlink will start and end with brackets; in between
290       // will be either a page name, a URL or both separated by a pipe.
291
292       // strip brackets and leading space
293       preg_match("/(\[\s*)(.+?)(\s*\])/", $bracketlink, $match);
294       // match the contents 
295       preg_match("/([^|]+)(\|)?([^|]+)?/", $match[2], $matches);
296
297       if (isset($matches[3])) {
298          // named link of the form  "[some link name | http://blippy.com/]"
299          $URL = trim($matches[3]);
300          $linkname = htmlspecialchars(trim($matches[1]));
301          $linktype = 'named';
302       } else {
303          // unnamed link of the form "[http://blippy.com/] or [wiki page]"
304          $URL = trim($matches[1]);
305          $linkname = '';
306          $linktype = 'simple';
307       }
308
309       if (IsWikiPage($dbi, $URL)) {
310          $link['type'] = "wiki-$linktype";
311          $link['link'] = LinkExistingWikiWord($URL, $linkname);
312       } elseif (preg_match("#^($AllowedProtocols):#", $URL)) {
313         // if it's an image, embed it; otherwise, it's a regular link
314          if (preg_match("/($InlineImages)$/i", $URL)) {
315             $link['type'] = "image-$linktype";
316             $link['link'] = LinkImage($URL, $linkname);
317          } else {
318             $link['type'] = "url-$linktype";
319             $link['link'] = LinkURL($URL, $linkname);
320          }
321       } elseif (preg_match("#^phpwiki:(.*)#", $URL, $match)) {
322          $link['type'] = "url-wiki-$linktype";
323          if(empty($linkname))
324             $linkname = htmlspecialchars($URL);
325          $link['link'] = "<a href=\"$ScriptUrl$match[1]\">$linkname</a>";
326       } elseif (preg_match("#^\d+$#", $URL)) {
327          $link['type'] = "reference-$linktype";
328          $link['link'] = $URL;
329       } else {
330          $link['type'] = "wiki-unknown-$linktype";
331          $link['link'] = LinkUnknownWikiWord($URL, $linkname);
332       }
333
334       return $link;
335    }
336
337
338    function ExtractWikiPageLinks($content)
339    {
340       global $WikiNameRegexp;
341
342       $wikilinks = array();
343       $numlines = count($content);
344       for($l = 0; $l < $numlines; $l++)
345       {
346          // remove escaped '['
347          $line = str_replace('[[', ' ', $content[$l]);
348
349          // bracket links (only type wiki-* is of interest)
350          $numBracketLinks = preg_match_all("/\[\s*([^\]|]+\|)?\s*(.+?)\s*\]/", $line, $brktlinks);
351          for ($i = 0; $i < $numBracketLinks; $i++) {
352             $link = ParseAndLink($brktlinks[0][$i]);
353             if (preg_match("#^wiki#", $link['type']))
354                $wikilinks[$brktlinks[2][$i]] = 1;
355
356             $brktlink = preg_quote($brktlinks[0][$i]);
357             $line = preg_replace("|$brktlink|", '', $line);
358          }
359
360          // BumpyText old-style wiki links
361          if (preg_match_all("/!?$WikiNameRegexp/", $line, $link)) {
362             for ($i = 0; isset($link[0][$i]); $i++) {
363                if($link[0][$i][0] <> '!')
364                   $wikilinks[$link[0][$i]] = 1;
365             }
366          }
367       }
368       return $wikilinks;
369    }      
370
371
372    function LinkRelatedPages($dbi, $pagename)
373    {
374       // currently not supported everywhere
375       if(!function_exists('GetWikiPageLinks'))
376          return '';
377
378       $links = GetWikiPageLinks($dbi, $pagename);
379
380       $txt = "<b>";
381       $txt .= sprintf (gettext ("%d best incoming links:"), NUM_RELATED_PAGES);
382       $txt .= "</b>\n";
383       for($i = 0; $i < NUM_RELATED_PAGES; $i++) {
384          if(isset($links['in'][$i])) {
385             list($name, $score) = $links['in'][$i];
386             $txt .= LinkExistingWikiWord($name) . " ($score), ";
387          }
388       }
389
390       $txt .= "\n<br><b>";
391       $txt .= sprintf (gettext ("%d best outgoing links:"), NUM_RELATED_PAGES);
392       $txt .= "</b>\n";
393       for($i = 0; $i < NUM_RELATED_PAGES; $i++) {
394          if(isset($links['out'][$i])) {
395             list($name, $score) = $links['out'][$i];
396             if(IsWikiPage($dbi, $name))
397                $txt .= LinkExistingWikiWord($name) . " ($score), ";
398          }
399       }
400
401       $txt .= "\n<br><b>";
402       $txt .= sprintf (gettext ("%d most popular nearby:"), NUM_RELATED_PAGES);
403       $txt .= "</b>\n";
404       for($i = 0; $i < NUM_RELATED_PAGES; $i++) {
405          if(isset($links['popular'][$i])) {
406             list($name, $score) = $links['popular'][$i];
407             $txt .= LinkExistingWikiWord($name) . " ($score), ";
408          }
409       }
410       
411       return $txt;
412    }
413
414    
415    # GeneratePage() -- takes $content and puts it in the template $template
416    # this function contains all the template logic
417    #
418    # $template ... name of the template (see config.php for list of names)
419    # $content ... html content to put into the page
420    # $name ... page title
421    # $hash ... if called while creating a wiki page, $hash points to
422    #           the $pagehash array of that wiki page.
423
424    function GeneratePage($template, $content, $name, $hash)
425    {
426       global $ScriptUrl, $AllowedProtocols, $templates;
427       global $datetimeformat, $dbi, $logo, $FieldSeparator;
428
429       if (!is_array($hash))
430          unset($hash);
431
432       function _dotoken ($id, $val, &$page) {
433          global $FieldSeparator;
434          $page = str_replace("$FieldSeparator#$id$FieldSeparator#",
435                                 $val, $page);
436       }
437
438       function _iftoken ($id, $condition, &$page) {
439          global $FieldSeparator;
440
441          // line based IF directive
442          $lineyes = "$FieldSeparator#IF $id$FieldSeparator#";
443          $lineno = "$FieldSeparator#IF !$id$FieldSeparator#";
444          // block based IF directive
445          $blockyes = "$FieldSeparator#IF:$id$FieldSeparator#";
446          $blockyesend = "$FieldSeparator#ENDIF:$id$FieldSeparator#";
447          $blockno = "$FieldSeparator#IF:!$id$FieldSeparator#";
448          $blocknoend = "$FieldSeparator#ENDIF:!$id$FieldSeparator#";
449
450          if ($condition) {
451             $page = str_replace($lineyes, '', $page);
452             $page = str_replace($blockyes, '', $page);
453             $page = str_replace($blockyesend, '', $page);
454             $page = preg_replace("/$blockno(.*?)$blocknoend/s", '', $page);
455             $page = ereg_replace("${lineno}[^\n]*\n", '', $page);
456          } else {
457             $page = str_replace($lineno, '', $page);
458             $page = str_replace($blockno, '', $page);
459             $page = str_replace($blocknoend, '', $page);
460             $page = preg_replace("/$blockyes(.*?)$blockyesend/s", '', $page);
461             $page = ereg_replace("${lineyes}[^\n]*\n", '', $page);
462          }
463       }
464
465       $page = join('', file($templates[$template]));
466       $page = str_replace('###', "$FieldSeparator#", $page);
467
468       // valid for all pagetypes
469       _iftoken('COPY', isset($hash['copy']), $page);
470       _iftoken('LOCK',  (isset($hash['flags']) &&
471                         ($hash['flags'] & FLAG_PAGE_LOCKED)), $page);
472       _iftoken('ADMIN', defined('WIKI_ADMIN'), $page);
473
474       _dotoken('SCRIPTURL', $ScriptUrl, $page);
475       _dotoken('PAGE', htmlspecialchars($name), $page);
476       _dotoken('ALLOWEDPROTOCOLS', $AllowedProtocols, $page);
477       _dotoken('LOGO', $logo, $page);
478       
479       // invalid for messages (search results, error messages)
480       if ($template != 'MESSAGE') {
481          _dotoken('PAGEURL', rawurlencode($name), $page);
482          _dotoken('LASTMODIFIED',
483                         date($datetimeformat, $hash['lastmodified']), $page);
484          _dotoken('LASTAUTHOR', $hash['author'], $page);
485          _dotoken('VERSION', $hash['version'], $page);
486          if (strstr($page, "$FieldSeparator#HITS$FieldSeparator#")) {
487             _dotoken('HITS', GetHitCount($dbi, $name), $page);
488          }
489          if (strstr($page, "$FieldSeparator#RELATEDPAGES$FieldSeparator#")) {
490             _dotoken('RELATEDPAGES', LinkRelatedPages($dbi, $name), $page);
491          }
492       }
493
494       // valid only for EditLinks
495       if ($template == 'EDITLINKS') {
496          for ($i = 1; $i <= NUM_LINKS; $i++) {
497             $ref = isset($hash['refs'][$i]) ? $hash['refs'][$i] : '';
498             _dotoken("R$i", $ref, $page);
499          }
500       }
501
502       _dotoken('CONTENT', $content, $page);
503       print $page;
504    }
505 ?>