]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/stdlib.php
Removed split_pagename for the page's <h1> until a better solution can be found....
[SourceForge/phpwiki.git] / lib / stdlib.php
1 <?php rcs_id('$Id: stdlib.php,v 1.21.2.10 2001-12-04 21:32:06 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       if (!defined('USE_LINK_ICONS')) {
137           return "<a href=\"$url\">$linktext</a>";
138       } else {
139             //ideally the link image would be specified by a map file
140             //similar to the interwiki.map
141             $linkproto = substr($url, 0, strrpos($url, ":"));
142             if ($linkproto == "mailto") {
143                 $linkimg = "/images/mailto.png";
144             } elseif ($linkproto == "http") { 
145                 $linkimg = "/images/http.png";
146             } elseif ($linkproto == "https") { 
147                 $linkimg = "/images/https.png";
148             } elseif ($linkproto == "ftp") { 
149                 $linkimg = "/images/ftp.png";
150             } else {
151                 $linkimg = "/images/http.png";
152             }
153       return "<a href=\"$url\"><img src=\"" . DATA_PATH . $linkimg . "\" alt=\"" . $linkproto . "\">$linktext</a>";
154       }
155    }
156
157    function LinkImage($url, $alt='[External Image]') {
158       global $ScriptUrl;
159       if(ereg('[<>"]', $url)) {
160          return "<b><u>BAD URL -- remove all of &lt;, &gt;, &quot;</u></b>";
161       }
162       return "<img src=\"$url\" ALT=\"$alt\">";
163    }
164
165    
166    function RenderQuickSearch($value = '') {
167       global $ScriptUrl;
168       return "<form action=\"$ScriptUrl\">\n" .
169              "<input type=text size=30 name=search value=\"$value\">\n" .
170              "<input type=submit value=\"". gettext("Search") .
171              "\"></form>\n";
172    }
173
174    function RenderFullSearch($value = '') {
175       global $ScriptUrl;
176       return "<form action=\"$ScriptUrl\">\n" .
177              "<input type=text size=30 name=full value=\"$value\">\n" .
178              "<input type=submit value=\"". gettext("Search") .
179              "\"></form>\n";
180    }
181
182    function RenderMostPopular() {
183       global $ScriptUrl, $dbi;
184       
185       $query = InitMostPopular($dbi, MOST_POPULAR_LIST_LENGTH);
186       $result = "<DL>\n";
187       while ($qhash = MostPopularNextMatch($dbi, $query)) {
188          $result .= "<DD>$qhash[hits] ... " . LinkExistingWikiWord($qhash['pagename']) . "\n";
189       }
190       $result .= "</DL>\n";
191       
192       return $result;
193    }
194
195
196    function ParseAdminTokens($line) {
197       global $ScriptUrl;
198       
199       while (preg_match("/%%ADMIN-INPUT-(.*?)-(\w+)%%/", $line, $matches)) {
200          $head = str_replace('_', ' ', $matches[2]);
201          $form = "<FORM ACTION=\"$ScriptUrl\" METHOD=POST>"
202                 ."$head: <INPUT NAME=$matches[1] SIZE=20> "
203                 ."<INPUT TYPE=SUBMIT VALUE=\"" . gettext("Go") . "\">"
204                 ."</FORM>";
205          $line = str_replace($matches[0], $form, $line);
206       }
207       return $line;
208    }
209
210    // converts spaces to tabs
211    function CookSpaces($pagearray) {
212       return preg_replace("/ {3,8}/", "\t", $pagearray);
213    }
214
215
216    class Stack {
217       var $items = array();
218       var $size = 0;
219
220       function push($item) {
221          $this->items[$this->size] = $item;
222          $this->size++;
223          return true;
224       }  
225    
226       function pop() {
227          if ($this->size == 0) {
228             return false; // stack is empty
229          }  
230          $this->size--;
231          return $this->items[$this->size];
232       }  
233    
234       function cnt() {
235          return $this->size;
236       }  
237
238       function top() {
239          if($this->size)
240             return $this->items[$this->size - 1];
241          else
242             return '';
243       }  
244
245    }  
246    // end class definition
247
248
249    // I couldn't move this to lib/config.php because it wasn't declared yet.
250    $stack = new Stack;
251
252    /* 
253       Wiki HTML output can, at any given time, be in only one mode.
254       It will be something like Unordered List, Preformatted Text,
255       plain text etc. When we change modes we have to issue close tags
256       for one mode and start tags for another.
257
258       $tag ... HTML tag to insert
259       $tagtype ... ZERO_LEVEL - close all open tags before inserting $tag
260                    NESTED_LEVEL - close tags until depths match
261       $level ... nesting level (depth) of $tag
262                  nesting is arbitrary limited to 10 levels
263    */
264
265    function SetHTMLOutputMode($tag, $tagtype, $level)
266    {
267       global $stack;
268       $retvar = '';
269
270       if ($level > 10) {
271           // arbitrarily limit tag nesting
272           //ExitWiki(gettext ("Nesting depth exceeded in SetHTMLOutputMode"));
273           // Now, instead of crapping out when we encounter a deeply
274           // nested list item, we just clamp the the maximum depth.
275           $level = 10;
276       }
277       
278       if ($tagtype == ZERO_LEVEL) {
279          // empty the stack until $level == 0;
280          if ($tag == $stack->top()) {
281             return; // same tag? -> nothing to do
282          }
283          while ($stack->cnt() > 0) {
284             $closetag = $stack->pop();
285             $retvar .= "</$closetag>\n";
286          }
287    
288          if ($tag) {
289             $retvar .= "<$tag>\n";
290             $stack->push($tag);
291          }
292
293
294       } elseif ($tagtype == NESTED_LEVEL) {
295          if ($level < $stack->cnt()) {
296             // $tag has fewer nestings (old: tabs) than stack,
297             // reduce stack to that tab count
298             while ($stack->cnt() > $level) {
299                $closetag = $stack->pop();
300                if ($closetag == false) {
301                   //echo "bounds error in tag stack";
302                   break;
303                }
304                $retvar .= "</$closetag>\n";
305             }
306
307             // if list type isn't the same,
308             // back up one more and push new tag
309             if ($tag != $stack->top()) {
310                $closetag = $stack->pop();
311                $retvar .= "</$closetag><$tag>\n";
312                $stack->push($tag);
313             }
314    
315          } elseif ($level > $stack->cnt()) {
316             // Test for and close top level elements which are not allowed to contain
317             // other block-level elements.
318             if ($stack->cnt() == 1 and
319                 preg_match('/^(p|pre|h\d)$/i', $stack->top()))
320             {
321                $closetag = $stack->pop();
322                $retvar .= "</$closetag>";
323             }
324
325             // we add the diff to the stack
326             // stack might be zero
327             if ($stack->cnt() < $level) {
328                while ($stack->cnt() < $level - 1) {
329                   // This is a bit of a hack:
330                   //
331                   // We're not nested deep enough, and have to make up
332                   // some kind of block element to nest within.
333                   //
334                   // Currently, this can only happen for nested list
335                   // element (either <ul> <ol> or <dl>).  What we used
336                   // to do here is to open extra lists of whatever
337                   // type was requested.  This would result in invalid
338                   // HTML, since and list is not allowed to contain
339                   // another list without first containing a list
340                   // item.  ("<ul><ul><li>Item</ul></ul>" is invalid.)
341                   //
342                   // So now, when we need extra list elements, we use
343                   // a <dl>, and open it with an empty <dd>.
344
345                   $retvar .= "<dl><dd>";
346                   $stack->push('dl');
347                }
348
349                $retvar .= "<$tag>\n";
350                $stack->push($tag);
351             }
352    
353          } else { // $level == $stack->cnt()
354             if ($tag == $stack->top()) {
355                return; // same tag? -> nothing to do
356             } else {
357                // different tag - close old one, add new one
358                $closetag = $stack->pop();
359                $retvar .= "</$closetag>\n";
360                $retvar .= "<$tag>\n";
361                $stack->push($tag);
362             }
363          }
364
365    
366       } else { // unknown $tagtype
367          ExitWiki ("Passed bad tag type value in SetHTMLOutputMode");
368       }
369
370       return $retvar;
371    }
372    // end SetHTMLOutputMode
373
374
375
376    function ParseAndLink($bracketlink) {
377       global $dbi, $ScriptUrl, $AllowedProtocols, $InlineImages;
378
379       // $bracketlink will start and end with brackets; in between
380       // will be either a page name, a URL or both separated by a pipe.
381
382       // strip brackets and leading space
383       preg_match("/(\[\s*)(.+?)(\s*\])/", $bracketlink, $match);
384       // match the contents 
385       preg_match("/([^|]+)(\|)?([^|]+)?/", $match[2], $matches);
386
387       if (isset($matches[3])) {
388          // named link of the form  "[some link name | http://blippy.com/]"
389          $URL = trim($matches[3]);
390          $linkname = htmlspecialchars(trim($matches[1]));
391          $linktype = 'named';
392       } else {
393          // unnamed link of the form "[http://blippy.com/] or [wiki page]"
394          $URL = trim($matches[1]);
395          $linkname = '';
396          $linktype = 'simple';
397       }
398
399       if (IsWikiPage($dbi, $URL)) {
400          $link['type'] = "wiki-$linktype";
401          $link['link'] = LinkExistingWikiWord($URL, $linkname);
402       } elseif (preg_match("#^($AllowedProtocols):#", $URL)) {
403         // if it's an image, embed it; otherwise, it's a regular link
404          if (preg_match("/($InlineImages)$/i", $URL)) {
405             $link['type'] = "image-$linktype";
406             $link['link'] = LinkImage($URL, $linkname);
407          } else {
408             $link['type'] = "url-$linktype";
409             $link['link'] = LinkURL($URL, $linkname);
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', 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 ?>