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