]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/stdlib.php
Fixed SF Bug #414789
[SourceForge/phpwiki.git] / lib / stdlib.php
1 <?php rcs_id('$Id: stdlib.php,v 1.21.2.5 2001-08-18 01:50:47 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           // Now, instead of crapping out when we encounter a deeply
187           // nested list item, we just clamp the the maximum depth.
188           $level = 10;
189       }
190       
191       if ($tagtype == ZERO_LEVEL) {
192          // empty the stack until $level == 0;
193          if ($tag == $stack->top()) {
194             return; // same tag? -> nothing to do
195          }
196          while ($stack->cnt() > 0) {
197             $closetag = $stack->pop();
198             $retvar .= "</$closetag>\n";
199          }
200    
201          if ($tag) {
202             $retvar .= "<$tag>\n";
203             $stack->push($tag);
204          }
205
206
207       } elseif ($tagtype == NESTED_LEVEL) {
208          if ($level < $stack->cnt()) {
209             // $tag has fewer nestings (old: tabs) than stack,
210             // reduce stack to that tab count
211             while ($stack->cnt() > $level) {
212                $closetag = $stack->pop();
213                if ($closetag == false) {
214                   //echo "bounds error in tag stack";
215                   break;
216                }
217                $retvar .= "</$closetag>\n";
218             }
219
220             // if list type isn't the same,
221             // back up one more and push new tag
222             if ($tag != $stack->top()) {
223                $closetag = $stack->pop();
224                $retvar .= "</$closetag><$tag>\n";
225                $stack->push($tag);
226             }
227    
228          } elseif ($level > $stack->cnt()) {
229             // Test for and close top level elements which are not allowed to contain
230             // other block-level elements.
231             if ($stack->cnt() == 1 and
232                 preg_match('/^(p|pre|h\d)$/i', $stack->top()))
233             {
234                $closetag = $stack->pop();
235                $retvar .= "</$closetag>";
236             }
237
238             // we add the diff to the stack
239             // stack might be zero
240             if ($stack->cnt() < $level) {
241                while ($stack->cnt() < $level - 1) {
242                   // This is a bit of a hack:
243                   //
244                   // We're not nested deep enough, and have to make up
245                   // some kind of block element to nest within.
246                   //
247                   // Currently, this can only happen for nested list
248                   // element (either <ul> <ol> or <dl>).  What we used
249                   // to do here is to open extra lists of whatever
250                   // type was requested.  This would result in invalid
251                   // HTML, since and list is not allowed to contain
252                   // another list without first containing a list
253                   // item.  ("<ul><ul><li>Item</ul></ul>" is invalid.)
254                   //
255                   // So now, when we need extra list elements, we use
256                   // a <dl>, and open it with an empty <dd>.
257
258                   $retvar .= "<dl><dd>";
259                   $stack->push('dl');
260                }
261
262                $retvar .= "<$tag>\n";
263                $stack->push($tag);
264             }
265    
266          } else { // $level == $stack->cnt()
267             if ($tag == $stack->top()) {
268                return; // same tag? -> nothing to do
269             } else {
270                // different tag - close old one, add new one
271                $closetag = $stack->pop();
272                $retvar .= "</$closetag>\n";
273                $retvar .= "<$tag>\n";
274                $stack->push($tag);
275             }
276          }
277
278    
279       } else { // unknown $tagtype
280          ExitWiki ("Passed bad tag type value in SetHTMLOutputMode");
281       }
282
283       return $retvar;
284    }
285    // end SetHTMLOutputMode
286
287
288
289    function ParseAndLink($bracketlink) {
290       global $dbi, $ScriptUrl, $AllowedProtocols, $InlineImages;
291
292       // $bracketlink will start and end with brackets; in between
293       // will be either a page name, a URL or both separated by a pipe.
294
295       // strip brackets and leading space
296       preg_match("/(\[\s*)(.+?)(\s*\])/", $bracketlink, $match);
297       // match the contents 
298       preg_match("/([^|]+)(\|)?([^|]+)?/", $match[2], $matches);
299
300       if (isset($matches[3])) {
301          // named link of the form  "[some link name | http://blippy.com/]"
302          $URL = trim($matches[3]);
303          $linkname = htmlspecialchars(trim($matches[1]));
304          $linktype = 'named';
305       } else {
306          // unnamed link of the form "[http://blippy.com/] or [wiki page]"
307          $URL = trim($matches[1]);
308          $linkname = '';
309          $linktype = 'simple';
310       }
311
312       if (IsWikiPage($dbi, $URL)) {
313          $link['type'] = "wiki-$linktype";
314          $link['link'] = LinkExistingWikiWord($URL, $linkname);
315       } elseif (preg_match("#^($AllowedProtocols):#", $URL)) {
316         // if it's an image, embed it; otherwise, it's a regular link
317          if (preg_match("/($InlineImages)$/i", $URL)) {
318             $link['type'] = "image-$linktype";
319             $link['link'] = LinkImage($URL, $linkname);
320          } else {
321             $link['type'] = "url-$linktype";
322             $link['link'] = LinkURL($URL, $linkname);
323          }
324       } elseif (preg_match("#^phpwiki:(.*)#", $URL, $match)) {
325          $link['type'] = "url-wiki-$linktype";
326          if(empty($linkname))
327             $linkname = htmlspecialchars($URL);
328          $link['link'] = "<a href=\"$ScriptUrl$match[1]\">$linkname</a>";
329       } elseif (preg_match("#^\d+$#", $URL)) {
330          $link['type'] = "reference-$linktype";
331          $link['link'] = $URL;
332       } else {
333          $link['type'] = "wiki-unknown-$linktype";
334          $link['link'] = LinkUnknownWikiWord($URL, $linkname);
335       }
336
337       return $link;
338    }
339
340
341    function ExtractWikiPageLinks($content)
342    {
343       global $WikiNameRegexp, $AllowedProtocols;
344
345       $wikilinks = array();
346       $numlines = count($content);
347       for($l = 0; $l < $numlines; $l++)
348       {
349          // remove escaped '['
350          $line = str_replace('[[', ' ', $content[$l]);
351
352          // bracket links (only type wiki-* is of interest)
353          $numBracketLinks = preg_match_all("/\[\s*([^\]|]+\|)?\s*(.+?)\s*\]/", $line, $brktlinks);
354          for ($i = 0; $i < $numBracketLinks; $i++) {
355             $link = ParseAndLink($brktlinks[0][$i]);
356             if (preg_match("#^wiki#", $link['type']))
357                $wikilinks[$brktlinks[2][$i]] = 1;
358
359             $brktlink = preg_quote($brktlinks[0][$i]);
360             $line = preg_replace("|$brktlink|", '', $line);
361          }
362
363          // Remove URLs (think about "http:a.b.com/WikiWords").
364          $line = preg_replace("/!?\b($AllowedProtocols):[^\s<>\[\]\"'()]*[^\s<>\[\]\"'(),.?]/",
365                               ' ', $line);
366          
367          // BumpyText old-style wiki links
368          if (preg_match_all("/!?$WikiNameRegexp/", $line, $link)) {
369             for ($i = 0; isset($link[0][$i]); $i++) {
370                if($link[0][$i][0] <> '!')
371                   $wikilinks[$link[0][$i]] = 1;
372             }
373          }
374       }
375       return $wikilinks;
376    }      
377
378
379    function LinkRelatedPages($dbi, $pagename)
380    {
381       // currently not supported everywhere
382       if(!function_exists('GetWikiPageLinks'))
383          return '';
384
385       $links = GetWikiPageLinks($dbi, $pagename);
386
387       $txt = "<b>";
388       $txt .= sprintf (gettext ("%d best incoming links:"), NUM_RELATED_PAGES);
389       $txt .= "</b>\n";
390       for($i = 0; $i < NUM_RELATED_PAGES; $i++) {
391          if(isset($links['in'][$i])) {
392             list($name, $score) = $links['in'][$i];
393             $txt .= LinkExistingWikiWord($name) . " ($score), ";
394          }
395       }
396
397       $txt .= "\n<br><b>";
398       $txt .= sprintf (gettext ("%d best outgoing links:"), NUM_RELATED_PAGES);
399       $txt .= "</b>\n";
400       for($i = 0; $i < NUM_RELATED_PAGES; $i++) {
401          if(isset($links['out'][$i])) {
402             list($name, $score) = $links['out'][$i];
403             if(IsWikiPage($dbi, $name))
404                $txt .= LinkExistingWikiWord($name) . " ($score), ";
405          }
406       }
407
408       $txt .= "\n<br><b>";
409       $txt .= sprintf (gettext ("%d most popular nearby:"), NUM_RELATED_PAGES);
410       $txt .= "</b>\n";
411       for($i = 0; $i < NUM_RELATED_PAGES; $i++) {
412          if(isset($links['popular'][$i])) {
413             list($name, $score) = $links['popular'][$i];
414             $txt .= LinkExistingWikiWord($name) . " ($score), ";
415          }
416       }
417       
418       return $txt;
419    }
420
421    
422    # GeneratePage() -- takes $content and puts it in the template $template
423    # this function contains all the template logic
424    #
425    # $template ... name of the template (see config.php for list of names)
426    # $content ... html content to put into the page
427    # $name ... page title
428    # $hash ... if called while creating a wiki page, $hash points to
429    #           the $pagehash array of that wiki page.
430
431    function GeneratePage($template, $content, $name, $hash)
432    {
433       global $ScriptUrl, $AllowedProtocols, $templates;
434       global $datetimeformat, $dbi, $logo, $FieldSeparator;
435
436       if (!is_array($hash))
437          unset($hash);
438
439       function _dotoken ($id, $val, &$page) {
440          global $FieldSeparator;
441          $page = str_replace("$FieldSeparator#$id$FieldSeparator#",
442                                 $val, $page);
443       }
444
445       function _iftoken ($id, $condition, &$page) {
446          global $FieldSeparator;
447
448          // line based IF directive
449          $lineyes = "$FieldSeparator#IF $id$FieldSeparator#";
450          $lineno = "$FieldSeparator#IF !$id$FieldSeparator#";
451          // block based IF directive
452          $blockyes = "$FieldSeparator#IF:$id$FieldSeparator#";
453          $blockyesend = "$FieldSeparator#ENDIF:$id$FieldSeparator#";
454          $blockno = "$FieldSeparator#IF:!$id$FieldSeparator#";
455          $blocknoend = "$FieldSeparator#ENDIF:!$id$FieldSeparator#";
456
457          if ($condition) {
458             $page = str_replace($lineyes, '', $page);
459             $page = str_replace($blockyes, '', $page);
460             $page = str_replace($blockyesend, '', $page);
461             $page = preg_replace("/$blockno(.*?)$blocknoend/s", '', $page);
462             $page = ereg_replace("${lineno}[^\n]*\n", '', $page);
463          } else {
464             $page = str_replace($lineno, '', $page);
465             $page = str_replace($blockno, '', $page);
466             $page = str_replace($blocknoend, '', $page);
467             $page = preg_replace("/$blockyes(.*?)$blockyesend/s", '', $page);
468             $page = ereg_replace("${lineyes}[^\n]*\n", '', $page);
469          }
470       }
471
472       $page = join('', file($templates[$template]));
473       $page = str_replace('###', "$FieldSeparator#", $page);
474
475       // valid for all pagetypes
476       _iftoken('COPY', isset($hash['copy']), $page);
477       _iftoken('LOCK',  (isset($hash['flags']) &&
478                         ($hash['flags'] & FLAG_PAGE_LOCKED)), $page);
479       _iftoken('ADMIN', defined('WIKI_ADMIN'), $page);
480
481       _dotoken('SCRIPTURL', $ScriptUrl, $page);
482       _dotoken('PAGE', htmlspecialchars($name), $page);
483       _dotoken('ALLOWEDPROTOCOLS', $AllowedProtocols, $page);
484       _dotoken('LOGO', $logo, $page);
485       
486       // invalid for messages (search results, error messages)
487       if ($template != 'MESSAGE') {
488          _dotoken('PAGEURL', rawurlencode($name), $page);
489          _dotoken('LASTMODIFIED',
490                         date($datetimeformat, $hash['lastmodified']), $page);
491          _dotoken('LASTAUTHOR', $hash['author'], $page);
492          _dotoken('VERSION', $hash['version'], $page);
493          if (strstr($page, "$FieldSeparator#HITS$FieldSeparator#")) {
494             _dotoken('HITS', GetHitCount($dbi, $name), $page);
495          }
496          if (strstr($page, "$FieldSeparator#RELATEDPAGES$FieldSeparator#")) {
497             _dotoken('RELATEDPAGES', LinkRelatedPages($dbi, $name), $page);
498          }
499       }
500
501       // valid only for EditLinks
502       if ($template == 'EDITLINKS') {
503          for ($i = 1; $i <= NUM_LINKS; $i++) {
504             $ref = isset($hash['refs'][$i]) ? $hash['refs'][$i] : '';
505             _dotoken("R$i", $ref, $page);
506          }
507       }
508
509       _dotoken('CONTENT', $content, $page);
510       print $page;
511    }
512 ?>