1 <?php rcs_id('$Id: stdlib.php,v 1.21.2.8 2001-12-02 21:30:36 carstenklapp Exp $');
4 Standard functions for Wiki functionality
6 pcre_fix_posix_classes($regexp)
8 LinkExistingWikiWord($wikiword, $linktext)
9 LinkUnknownWikiWord($wikiword, $linktext)
10 LinkURL($url, $linktext)
12 RenderQuickSearch($value)
13 RenderFullSearch($value)
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)
26 function ExitWiki($errormsg)
31 if($exitwiki) // just in case CloseDataBase calls us
38 print "<P><hr noshade><h2>" . gettext("WikiFatalError") . "</h2>\n";
40 print "\n</BODY></HTML>";
47 * pcre_fix_posix_classes is required to autosplit_wikiwords.
48 * Called by the split_pagename function.
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'))
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"
63 $keys = join('|', array_keys($classes));
65 return preg_replace("/\[:($keys):]/e", '$classes["\1"]', $regexp);
70 * Split WikiWords in page names.
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
76 * @param $page string The page name.
78 * @return string The split name.
80 function split_pagename ($page) {
82 if (preg_match("/\s/", $page))
83 return $page; // Already split --- don't split any more.
85 // FIXME: this algorithm is Anglo-centric.
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
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:]])/';
97 foreach ($RE as $key => $val)
98 $RE[$key] = pcre_fix_posix_classes($val);
101 foreach ($RE as $regexp)
102 $page = preg_replace($regexp, '\\1 \\2', $page);
107 function LinkExistingWikiWord($wikiword, $linktext='') {
109 $enc_word = rawurlencode($wikiword);
111 $linktext = htmlspecialchars($wikiword);
112 if (defined("autosplit_wikiwords"))
113 $linktext=split_pagename($linktext);
115 return "<a href=\"$ScriptUrl?$enc_word\">$linktext</a>";
118 function LinkUnknownWikiWord($wikiword, $linktext='') {
120 $enc_word = rawurlencode($wikiword);
122 $linktext = htmlspecialchars($wikiword);
123 if (defined("autosplit_wikiwords"))
124 $linktext=split_pagename($linktext);
126 return "<u>$linktext</u><a href=\"$ScriptUrl?edit=$enc_word\">?</a>";
129 function LinkURL($url, $linktext='') {
131 if(ereg("[<>\"]", $url)) {
132 return "<b><u>BAD URL -- remove all of <, >, "</u></b>";
135 $linktext = htmlspecialchars($url);
136 return "<a href=\"$url\">$linktext</a>";
139 function LinkImage($url, $alt='[External Image]') {
141 if(ereg('[<>"]', $url)) {
142 return "<b><u>BAD URL -- remove all of <, >, "</u></b>";
144 return "<img src=\"$url\" ALT=\"$alt\">";
148 function RenderQuickSearch($value = '') {
150 return "<form action=\"$ScriptUrl\">\n" .
151 "<input type=text size=30 name=search value=\"$value\">\n" .
152 "<input type=submit value=\"". gettext("Search") .
156 function RenderFullSearch($value = '') {
158 return "<form action=\"$ScriptUrl\">\n" .
159 "<input type=text size=30 name=full value=\"$value\">\n" .
160 "<input type=submit value=\"". gettext("Search") .
164 function RenderMostPopular() {
165 global $ScriptUrl, $dbi;
167 $query = InitMostPopular($dbi, MOST_POPULAR_LIST_LENGTH);
169 while ($qhash = MostPopularNextMatch($dbi, $query)) {
170 $result .= "<DD>$qhash[hits] ... " . LinkExistingWikiWord($qhash['pagename']) . "\n";
172 $result .= "</DL>\n";
178 function ParseAdminTokens($line) {
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") . "\">"
187 $line = str_replace($matches[0], $form, $line);
192 // converts spaces to tabs
193 function CookSpaces($pagearray) {
194 return preg_replace("/ {3,8}/", "\t", $pagearray);
199 var $items = array();
202 function push($item) {
203 $this->items[$this->size] = $item;
209 if ($this->size == 0) {
210 return false; // stack is empty
213 return $this->items[$this->size];
222 return $this->items[$this->size - 1];
228 // end class definition
231 // I couldn't move this to lib/config.php because it wasn't declared yet.
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.
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
247 function SetHTMLOutputMode($tag, $tagtype, $level)
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.
260 if ($tagtype == ZERO_LEVEL) {
261 // empty the stack until $level == 0;
262 if ($tag == $stack->top()) {
263 return; // same tag? -> nothing to do
265 while ($stack->cnt() > 0) {
266 $closetag = $stack->pop();
267 $retvar .= "</$closetag>\n";
271 $retvar .= "<$tag>\n";
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";
286 $retvar .= "</$closetag>\n";
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";
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()))
303 $closetag = $stack->pop();
304 $retvar .= "</$closetag>";
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:
313 // We're not nested deep enough, and have to make up
314 // some kind of block element to nest within.
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.)
324 // So now, when we need extra list elements, we use
325 // a <dl>, and open it with an empty <dd>.
327 $retvar .= "<dl><dd>";
331 $retvar .= "<$tag>\n";
335 } else { // $level == $stack->cnt()
336 if ($tag == $stack->top()) {
337 return; // same tag? -> nothing to do
339 // different tag - close old one, add new one
340 $closetag = $stack->pop();
341 $retvar .= "</$closetag>\n";
342 $retvar .= "<$tag>\n";
348 } else { // unknown $tagtype
349 ExitWiki ("Passed bad tag type value in SetHTMLOutputMode");
354 // end SetHTMLOutputMode
358 function ParseAndLink($bracketlink) {
359 global $dbi, $ScriptUrl, $AllowedProtocols, $InlineImages;
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.
364 // strip brackets and leading space
365 preg_match("/(\[\s*)(.+?)(\s*\])/", $bracketlink, $match);
366 // match the contents
367 preg_match("/([^|]+)(\|)?([^|]+)?/", $match[2], $matches);
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]));
375 // unnamed link of the form "[http://blippy.com/] or [wiki page]"
376 $URL = trim($matches[1]);
378 $linktype = 'simple';
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);
390 $link['type'] = "url-$linktype";
392 if (!defined("USE_LINK_ICONS")) {
393 $link['link'] = LinkURL($URL, $linkname);
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);
408 $link['link'] = LinkURL($URL, $linkname);
411 } elseif (preg_match("#^phpwiki:(.*)#", $URL, $match)) {
412 $link['type'] = "url-wiki-$linktype";
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;
420 $link['type'] = "wiki-unknown-$linktype";
421 $link['link'] = LinkUnknownWikiWord($URL, $linkname);
428 function ExtractWikiPageLinks($content)
430 global $WikiNameRegexp, $AllowedProtocols;
432 $wikilinks = array();
433 $numlines = count($content);
434 for($l = 0; $l < $numlines; $l++)
436 // remove escaped '['
437 $line = str_replace('[[', ' ', $content[$l]);
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;
446 $brktlink = preg_quote($brktlinks[0][$i]);
447 $line = preg_replace("|$brktlink|", '', $line);
450 // Remove URLs (think about "http:a.b.com/WikiWords").
451 $line = preg_replace("/!?\b($AllowedProtocols):[^\s<>\[\]\"'()]*[^\s<>\[\]\"'(),.?]/",
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;
466 function LinkRelatedPages($dbi, $pagename)
468 // currently not supported everywhere
469 if(!function_exists('GetWikiPageLinks'))
472 $links = GetWikiPageLinks($dbi, $pagename);
475 $txt .= sprintf (gettext ("%d best incoming links:"), NUM_RELATED_PAGES);
477 for($i = 0; $i < NUM_RELATED_PAGES; $i++) {
478 if(isset($links['in'][$i])) {
479 list($name, $score) = $links['in'][$i];
483 $txt .= LinkExistingWikiWord($name) . " ($score)";
488 $txt .= sprintf (gettext ("%d best outgoing links:"), NUM_RELATED_PAGES);
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))
497 $txt .= LinkExistingWikiWord($name) . " ($score)";
502 $txt .= sprintf (gettext ("%d most popular nearby:"), NUM_RELATED_PAGES);
504 for($i = 0; $i < NUM_RELATED_PAGES; $i++) {
505 if(isset($links['popular'][$i])) {
506 list($name, $score) = $links['popular'][$i];
510 $txt .= LinkExistingWikiWord($name) . " ($score)";
518 # GeneratePage() -- takes $content and puts it in the template $template
519 # this function contains all the template logic
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.
527 function GeneratePage($template, $content, $name, $hash)
529 global $ScriptUrl, $AllowedProtocols, $templates;
530 global $datetimeformat, $dbi, $logo, $FieldSeparator;
532 if (!is_array($hash))
535 function _dotoken ($id, $val, &$page) {
536 global $FieldSeparator;
537 $page = str_replace("$FieldSeparator#$id$FieldSeparator#",
541 function _iftoken ($id, $condition, &$page) {
542 global $FieldSeparator;
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#";
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);
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);
568 $page = join('', file($templates[$template]));
569 $page = str_replace('###', "$FieldSeparator#", $page);
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);
577 _dotoken('SCRIPTURL', $ScriptUrl, $page);
578 _dotoken('PAGE', split_pagename(htmlspecialchars($name)), $page);
579 _dotoken('ALLOWEDPROTOCOLS', $AllowedProtocols, $page);
580 _dotoken('LOGO', $logo, $page);
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);
592 if (strstr($page, "$FieldSeparator#RELATEDPAGES$FieldSeparator#")) {
593 _dotoken('RELATEDPAGES', LinkRelatedPages($dbi, $name), $page);
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);
605 _dotoken('CONTENT', $content, $page);