1 <?php rcs_id('$Id: stdlib.php,v 1.21.2.6 2001-11-08 22:20:19 dairiki Exp $');
4 Standard functions for Wiki functionality
6 LinkExistingWikiWord($wikiword, $linktext)
7 LinkUnknownWikiWord($wikiword, $linktext)
8 LinkURL($url, $linktext)
10 RenderQuickSearch($value)
11 RenderFullSearch($value)
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)
24 function ExitWiki($errormsg)
29 if($exitwiki) // just in case CloseDataBase calls us
36 print "<P><hr noshade><h2>" . gettext("WikiFatalError") . "</h2>\n";
38 print "\n</BODY></HTML>";
44 function LinkExistingWikiWord($wikiword, $linktext='') {
46 $enc_word = rawurlencode($wikiword);
48 $linktext = htmlspecialchars($wikiword);
49 return "<a href=\"$ScriptUrl?$enc_word\">$linktext</a>";
52 function LinkUnknownWikiWord($wikiword, $linktext='') {
54 $enc_word = rawurlencode($wikiword);
56 $linktext = htmlspecialchars($wikiword);
57 return "<u>$linktext</u><a href=\"$ScriptUrl?edit=$enc_word\">?</a>";
60 function LinkURL($url, $linktext='') {
62 if(ereg("[<>\"]", $url)) {
63 return "<b><u>BAD URL -- remove all of <, >, "</u></b>";
66 $linktext = htmlspecialchars($url);
67 return "<a href=\"$url\">$linktext</a>";
70 function LinkImage($url, $alt='[External Image]') {
72 if(ereg('[<>"]', $url)) {
73 return "<b><u>BAD URL -- remove all of <, >, "</u></b>";
75 return "<img src=\"$url\" ALT=\"$alt\">";
79 function RenderQuickSearch($value = '') {
81 return "<form action=\"$ScriptUrl\">\n" .
82 "<input type=text size=30 name=search value=\"$value\">\n" .
83 "<input type=submit value=\"". gettext("Search") .
87 function RenderFullSearch($value = '') {
89 return "<form action=\"$ScriptUrl\">\n" .
90 "<input type=text size=30 name=full value=\"$value\">\n" .
91 "<input type=submit value=\"". gettext("Search") .
95 function RenderMostPopular() {
96 global $ScriptUrl, $dbi;
98 $query = InitMostPopular($dbi, MOST_POPULAR_LIST_LENGTH);
100 while ($qhash = MostPopularNextMatch($dbi, $query)) {
101 $result .= "<DD>$qhash[hits] ... " . LinkExistingWikiWord($qhash['pagename']) . "\n";
103 $result .= "</DL>\n";
109 function ParseAdminTokens($line) {
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") . "\">"
118 $line = str_replace($matches[0], $form, $line);
123 // converts spaces to tabs
124 function CookSpaces($pagearray) {
125 return preg_replace("/ {3,8}/", "\t", $pagearray);
130 var $items = array();
133 function push($item) {
134 $this->items[$this->size] = $item;
140 if ($this->size == 0) {
141 return false; // stack is empty
144 return $this->items[$this->size];
153 return $this->items[$this->size - 1];
159 // end class definition
162 // I couldn't move this to lib/config.php because it wasn't declared yet.
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.
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
178 function SetHTMLOutputMode($tag, $tagtype, $level)
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.
191 if ($tagtype == ZERO_LEVEL) {
192 // empty the stack until $level == 0;
193 if ($tag == $stack->top()) {
194 return; // same tag? -> nothing to do
196 while ($stack->cnt() > 0) {
197 $closetag = $stack->pop();
198 $retvar .= "</$closetag>\n";
202 $retvar .= "<$tag>\n";
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";
217 $retvar .= "</$closetag>\n";
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";
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()))
234 $closetag = $stack->pop();
235 $retvar .= "</$closetag>";
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:
244 // We're not nested deep enough, and have to make up
245 // some kind of block element to nest within.
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.)
255 // So now, when we need extra list elements, we use
256 // a <dl>, and open it with an empty <dd>.
258 $retvar .= "<dl><dd>";
262 $retvar .= "<$tag>\n";
266 } else { // $level == $stack->cnt()
267 if ($tag == $stack->top()) {
268 return; // same tag? -> nothing to do
270 // different tag - close old one, add new one
271 $closetag = $stack->pop();
272 $retvar .= "</$closetag>\n";
273 $retvar .= "<$tag>\n";
279 } else { // unknown $tagtype
280 ExitWiki ("Passed bad tag type value in SetHTMLOutputMode");
285 // end SetHTMLOutputMode
289 function ParseAndLink($bracketlink) {
290 global $dbi, $ScriptUrl, $AllowedProtocols, $InlineImages;
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.
295 // strip brackets and leading space
296 preg_match("/(\[\s*)(.+?)(\s*\])/", $bracketlink, $match);
297 // match the contents
298 preg_match("/([^|]+)(\|)?([^|]+)?/", $match[2], $matches);
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]));
306 // unnamed link of the form "[http://blippy.com/] or [wiki page]"
307 $URL = trim($matches[1]);
309 $linktype = 'simple';
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);
321 $link['type'] = "url-$linktype";
322 $link['link'] = LinkURL($URL, $linkname);
324 } elseif (preg_match("#^phpwiki:(.*)#", $URL, $match)) {
325 $link['type'] = "url-wiki-$linktype";
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;
333 $link['type'] = "wiki-unknown-$linktype";
334 $link['link'] = LinkUnknownWikiWord($URL, $linkname);
341 function ExtractWikiPageLinks($content)
343 global $WikiNameRegexp, $AllowedProtocols;
345 $wikilinks = array();
346 $numlines = count($content);
347 for($l = 0; $l < $numlines; $l++)
349 // remove escaped '['
350 $line = str_replace('[[', ' ', $content[$l]);
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;
359 $brktlink = preg_quote($brktlinks[0][$i]);
360 $line = preg_replace("|$brktlink|", '', $line);
363 // Remove URLs (think about "http:a.b.com/WikiWords").
364 $line = preg_replace("/!?\b($AllowedProtocols):[^\s<>\[\]\"'()]*[^\s<>\[\]\"'(),.?]/",
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;
379 function LinkRelatedPages($dbi, $pagename)
381 // currently not supported everywhere
382 if(!function_exists('GetWikiPageLinks'))
385 $links = GetWikiPageLinks($dbi, $pagename);
388 $txt .= sprintf (gettext ("%d best incoming links:"), NUM_RELATED_PAGES);
390 for($i = 0; $i < NUM_RELATED_PAGES; $i++) {
391 if(isset($links['in'][$i])) {
392 list($name, $score) = $links['in'][$i];
396 $txt .= LinkExistingWikiWord($name) . " ($score)";
401 $txt .= sprintf (gettext ("%d best outgoing links:"), NUM_RELATED_PAGES);
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))
410 $txt .= LinkExistingWikiWord($name) . " ($score)";
415 $txt .= sprintf (gettext ("%d most popular nearby:"), NUM_RELATED_PAGES);
417 for($i = 0; $i < NUM_RELATED_PAGES; $i++) {
418 if(isset($links['popular'][$i])) {
419 list($name, $score) = $links['popular'][$i];
423 $txt .= LinkExistingWikiWord($name) . " ($score)";
431 # GeneratePage() -- takes $content and puts it in the template $template
432 # this function contains all the template logic
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.
440 function GeneratePage($template, $content, $name, $hash)
442 global $ScriptUrl, $AllowedProtocols, $templates;
443 global $datetimeformat, $dbi, $logo, $FieldSeparator;
445 if (!is_array($hash))
448 function _dotoken ($id, $val, &$page) {
449 global $FieldSeparator;
450 $page = str_replace("$FieldSeparator#$id$FieldSeparator#",
454 function _iftoken ($id, $condition, &$page) {
455 global $FieldSeparator;
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#";
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);
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);
481 $page = join('', file($templates[$template]));
482 $page = str_replace('###', "$FieldSeparator#", $page);
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);
490 _dotoken('SCRIPTURL', $ScriptUrl, $page);
491 _dotoken('PAGE', htmlspecialchars($name), $page);
492 _dotoken('ALLOWEDPROTOCOLS', $AllowedProtocols, $page);
493 _dotoken('LOGO', $logo, $page);
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);
505 if (strstr($page, "$FieldSeparator#RELATEDPAGES$FieldSeparator#")) {
506 _dotoken('RELATEDPAGES', LinkRelatedPages($dbi, $name), $page);
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);
518 _dotoken('CONTENT', $content, $page);