]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/stdlib.php
2004-12-19 01:02 rurban
[SourceForge/phpwiki.git] / lib / stdlib.php
1 <?php rcs_id('$Id: stdlib.php,v 1.21.2.12.2.3 2005-01-07 14:02:28 rurban 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             if($i>0) {
394                $txt .= ", ";
395             }
396             $txt .= LinkExistingWikiWord($name) . " ($score)";
397          }
398       }
399
400       $txt .= "\n<br><b>";
401       $txt .= sprintf (gettext ("%d best outgoing links:"), NUM_RELATED_PAGES);
402       $txt .= "</b>\n";
403       for($i = 0; $i < NUM_RELATED_PAGES; $i++) {
404          if(isset($links['out'][$i])) {
405             list($name, $score) = $links['out'][$i];
406             if(IsWikiPage($dbi, $name))
407                if($i>0) {
408                   $txt .= ", ";
409                }
410                $txt .= LinkExistingWikiWord($name) . " ($score)";
411          }
412       }
413
414       $txt .= "\n<br><b>";
415       $txt .= sprintf (gettext ("%d most popular nearby:"), NUM_RELATED_PAGES);
416       $txt .= "</b>\n";
417       for($i = 0; $i < NUM_RELATED_PAGES; $i++) {
418          if(isset($links['popular'][$i])) {
419             list($name, $score) = $links['popular'][$i];
420             if($i>0) {
421                $txt .= ", ";
422             }
423             $txt .= LinkExistingWikiWord($name) . " ($score)";
424          }
425       }
426       
427       return $txt;
428    }
429
430    
431    # GeneratePage() -- takes $content and puts it in the template $template
432    # this function contains all the template logic
433    #
434    # $template ... name of the template (see config.php for list of names)
435    # $content ... html content to put into the page
436    # $name ... page title
437    # $hash ... if called while creating a wiki page, $hash points to
438    #           the $pagehash array of that wiki page.
439
440    function GeneratePage($template, $content, $name, $hash)
441    {
442       global $ScriptUrl, $AllowedProtocols, $templates;
443       global $datetimeformat, $dbi, $logo, $FieldSeparator;
444
445       if (!is_array($hash))
446          unset($hash);
447
448       function _dotoken ($id, $val, &$page) {
449          global $FieldSeparator;
450          $page = str_replace("$FieldSeparator#$id$FieldSeparator#",
451                                 $val, $page);
452       }
453
454       function _iftoken ($id, $condition, &$page) {
455          global $FieldSeparator;
456
457          // line based IF directive
458          $lineyes = "$FieldSeparator#IF $id$FieldSeparator#";
459          $lineno = "$FieldSeparator#IF !$id$FieldSeparator#";
460          // block based IF directive
461          $blockyes = "$FieldSeparator#IF:$id$FieldSeparator#";
462          $blockyesend = "$FieldSeparator#ENDIF:$id$FieldSeparator#";
463          $blockno = "$FieldSeparator#IF:!$id$FieldSeparator#";
464          $blocknoend = "$FieldSeparator#ENDIF:!$id$FieldSeparator#";
465
466          if ($condition) {
467             $page = str_replace($lineyes, '', $page);
468             $page = str_replace($blockyes, '', $page);
469             $page = str_replace($blockyesend, '', $page);
470             $page = preg_replace("/$blockno(.*?)$blocknoend/s", '', $page);
471             $page = ereg_replace("${lineno}[^\n]*\n", '', $page);
472          } else {
473             $page = str_replace($lineno, '', $page);
474             $page = str_replace($blockno, '', $page);
475             $page = str_replace($blocknoend, '', $page);
476             $page = preg_replace("/$blockyes(.*?)$blockyesend/s", '', $page);
477             $page = ereg_replace("${lineyes}[^\n]*\n", '', $page);
478          }
479       }
480
481       $page = join('', file($templates[$template]));
482       $page = str_replace('###', "$FieldSeparator#", $page);
483
484       // valid for all pagetypes
485       _iftoken('COPY', isset($hash['copy']), $page);
486       _iftoken('LOCK',  (isset($hash['flags']) &&
487                         ($hash['flags'] & FLAG_PAGE_LOCKED)), $page);
488       _iftoken('ADMIN', defined('WIKI_ADMIN'), $page);
489
490       _dotoken('SCRIPTURL', $ScriptUrl, $page);
491       _dotoken('PAGE', htmlspecialchars($name), $page);
492       _dotoken('ALLOWEDPROTOCOLS', $AllowedProtocols, $page);
493       _dotoken('LOGO', $logo, $page);
494       
495       // invalid for messages (search results, error messages)
496       if ($template != 'MESSAGE') {
497          _dotoken('PAGEURL', rawurlencode($name), $page);
498          _dotoken('LASTMODIFIED',
499                         date($datetimeformat, $hash['lastmodified']), $page);
500          _dotoken('LASTAUTHOR', $hash['author'], $page);
501          _dotoken('VERSION', $hash['version'], $page);
502          if (strstr($page, "$FieldSeparator#HITS$FieldSeparator#")) {
503             _dotoken('HITS', GetHitCount($dbi, $name), $page);
504          }
505          if (strstr($page, "$FieldSeparator#RELATEDPAGES$FieldSeparator#")) {
506             _dotoken('RELATEDPAGES', LinkRelatedPages($dbi, $name), $page);
507          }
508       }
509
510       // valid only for EditLinks
511       if ($template == 'EDITLINKS') {
512          for ($i = 1; $i <= NUM_LINKS; $i++) {
513             $ref = isset($hash['refs'][$i]) ? $hash['refs'][$i] : '';
514             _dotoken("R$i", $ref, $page);
515          }
516       }
517
518       _dotoken('CONTENT', $content, $page);
519       print $page;
520    }
521 ?>