]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/stdlib.php
Bug fix: empty square brackets lead to "Fatal Database Error" and
[SourceForge/phpwiki.git] / lib / stdlib.php
1 <?php rcs_id('$Id: stdlib.php,v 1.59 2001-12-12 23:51:25 dairiki Exp $');
2
3    /*
4       Standard functions for Wiki functionality
5          WikiURL($pagename, $args, $abs)
6          LinkWikiWord($wikiword, $linktext) 
7          LinkExistingWikiWord($wikiword, $linktext) 
8          LinkUnknownWikiWord($wikiword, $linktext) 
9          LinkURL($url, $linktext)
10          LinkImage($url, $alt)
11          LinkInterWikiLink($link, $linktext)
12          CookSpaces($pagearray) 
13          class Stack (push(), pop(), cnt(), top())
14          UpdateRecentChanges($dbi, $pagename, $isnewpage) 
15          ParseAndLink($bracketlink)
16          ExtractWikiPageLinks($content)
17          LinkRelatedPages($dbi, $pagename)
18    */
19
20
21    function DataURL($url) {
22       if (preg_match('@^(\w+:|/)@', $url))
23          return $url;
24       return SERVER_URL . DATA_PATH . "/$url";
25    }
26           
27 function WikiURL($pagename, $args = '', $get_abs_url = false) {
28     if (is_array($args)) {
29         $enc_args = array();
30         foreach  ($args as $key => $val) {
31             $enc_args[] = urlencode($key) . '=' . urlencode($val);
32         }
33         $args = join('&', $enc_args);
34     }
35
36     if (USE_PATH_INFO) {
37         $url = $get_abs_url ? SERVER_URL . VIRTUAL_PATH . "/" : '';
38         $url .= rawurlencode($pagename);
39         if ($args)
40             $url .= "?$args";
41     }
42     else {
43         $url = $get_abs_url ? SERVER_URL . SCRIPT_NAME : basename(SCRIPT_NAME);
44         $url .= "?pagename=" . rawurlencode($pagename);
45         if ($args)
46             $url .= "&$args";
47     }
48
49     return $url;
50 }
51
52 define('NO_END_TAG_PAT',
53        '/^' . join('|', array('area', 'base', 'basefont',
54                               'br', 'col', 'frame',
55                               'hr', 'img', 'input',
56                               'isindex', 'link', 'meta',
57                               'param')) . '$/i');
58
59 function StartTag($tag, $args = '')
60 {
61    $s = "<$tag";
62    if (is_array($args))
63    {
64       while (list($key, $val) = each($args))
65       {
66          if (is_string($val) || is_numeric($val))
67             $s .= sprintf(' %s="%s"', $key, htmlspecialchars($val));
68          else if ($val)
69             $s .= " $key=\"$key\"";
70       }
71    }
72    return "$s>";
73 }
74
75    
76 function Element($tag, $args = '', $content = '')
77 {
78     $html = "<$tag";
79     if (!is_array($args)) {
80         $content = $args;
81         $args = false;
82     }
83     $html = StartTag($tag, $args);
84     if (preg_match(NO_END_TAG_PAT, $tag)) {
85         assert(! $content);
86         return preg_replace('/>$/', " />", $html);
87     } else {
88         $html .= $content;
89         $html .= "</$tag>";//FIXME: newline might not always be desired.
90     }
91     return $html;
92 }
93
94 function QElement($tag, $args = '', $content = '')
95 {
96     if (is_array($args))
97         return Element($tag, $args, htmlspecialchars($content));
98     else {
99         $content = $args;
100         return Element($tag, htmlspecialchars($content));
101     }
102 }
103
104 function IconForLink($protocol_or_url) {
105     global $URL_LINK_ICONS;
106
107     list ($proto) = explode(':', $protocol_or_url, 2);
108     
109     if (isset($URL_LINK_ICONS[$proto])) {
110         $linkimg = $URL_LINK_ICONS[$proto];
111     }
112     elseif (isset($URL_LINK_ICONS['*'])) {
113         $linkimg = $URL_LINK_ICONS['*'];
114     }
115
116     if (empty($linkimg))
117         return '';
118     
119     return Element('img', array('src' => DataURL($linkimg),
120                                 'alt' => $proto,
121                                 'class' => 'linkicon'));
122 }
123     
124    
125 function LinkURL($url, $linktext = '') {
126     // FIXME: Is this needed (or sufficient?)
127     if(ereg("[<>\"]", $url)) {
128         return Element('strong',
129                        QElement('u', array('class' => 'baduri'),
130                                 _('BAD URL -- remove all of <, >, "'))); //"
131     }
132
133     $attr['href'] = $url;
134
135     if (empty($linktext)) {
136         $linktext = $url;
137         $attr['class'] = 'rawurl';
138     }
139     else {
140         $attr['class'] = 'namedurl';
141     }
142
143     return Element('a', $attr,
144                    IconForLink($url) . htmlspecialchars($linktext));
145 }
146
147 function LinkWikiWord($wikiword, $linktext='') {
148     global $dbi;
149     if ($dbi->isWikiPage($wikiword))
150         return LinkExistingWikiWord($wikiword, $linktext);
151     else
152         return LinkUnknownWikiWord($wikiword, $linktext);
153 }
154
155     
156    function LinkExistingWikiWord($wikiword, $linktext='') {
157       if (empty($linktext)) {
158           $linktext = $wikiword;
159           if (defined("autosplit_wikiwords"))
160               $linktext=split_pagename($linktext);
161          $class = 'wiki';
162       }
163       else
164           $class = 'named-wiki';
165       
166       return QElement('a', array('href' => WikiURL($wikiword),
167                                  'class' => $class),
168                      $linktext);
169    }
170
171    function LinkUnknownWikiWord($wikiword, $linktext='') {
172       if (empty($linktext)) {
173           $linktext = $wikiword;
174           if (defined("autosplit_wikiwords"))
175               $linktext=split_pagename($linktext);
176           $class = 'wikiunknown';
177       }
178       else {
179           $class = 'named-wikiunknown';
180       }
181
182       return Element('span', array('class' => $class),
183                      QElement('a',
184                               array('href' => WikiURL($wikiword, array('action' => 'edit'))),
185                               '?')
186                      . Element('u', $linktext));
187    }
188
189    function LinkImage($url, $alt='[External Image]') {
190       // FIXME: Is this needed (or sufficient?)
191       //  As long as the src in htmlspecialchars()ed I think it's safe.
192       if(ereg('[<>"]', $url)) {
193          return Element('strong',
194                         QElement('u', array('class' => 'baduri'),
195                                  'BAD URL -- remove all of <, >, "'));
196       }
197       return Element('img', array('src' => $url, 'alt' => $alt));
198    }
199
200    // converts spaces to tabs
201    function CookSpaces($pagearray) {
202       return preg_replace("/ {3,8}/", "\t", $pagearray);
203    }
204
205
206    class Stack {
207       var $items = array();
208       var $size = 0;
209
210       function push($item) {
211          $this->items[$this->size] = $item;
212          $this->size++;
213          return true;
214       }  
215    
216       function pop() {
217          if ($this->size == 0) {
218             return false; // stack is empty
219          }  
220          $this->size--;
221          return $this->items[$this->size];
222       }  
223    
224       function cnt() {
225          return $this->size;
226       }  
227
228       function top() {
229          if($this->size)
230             return $this->items[$this->size - 1];
231          else
232             return '';
233       }  
234
235    }  
236    // end class definition
237
238
239    function MakeWikiForm ($pagename, $args, $class, $button_text = '')
240    {
241       $formargs['action'] = USE_PATH_INFO ? WikiURL($pagename) : SCRIPT_NAME;
242       $formargs['method'] = 'get';
243       $formargs['class'] = $class;
244       
245       $contents = '';
246       $input_seen = 0;
247       
248       while (list($key, $val) = each($args))
249       {
250          $a = array('name' => $key, 'value' => $val, 'type' => 'hidden');
251          
252          if (preg_match('/^ (\d*) \( (.*) \) ((upload)?) $/xi', $val, $m))
253          {
254             $input_seen++;
255             $a['type'] = 'text';
256             $a['size'] = $m[1] ? $m[1] : 30;
257             $a['value'] = $m[2];
258             if ($m[3])
259             {
260                $a['type'] = 'file';
261                $formargs['enctype'] = 'multipart/form-data';
262                $contents .= Element('input',
263                                     array('name' => 'MAX_FILE_SIZE',
264                                           'value' => MAX_UPLOAD_SIZE,
265                                           'type' => 'hidden'));
266                $formargs['method'] = 'post';
267             }
268          }
269
270          $contents .= Element('input', $a);
271       }
272
273       $row = Element('td', $contents);
274       
275       if (!empty($button_text)) {
276          $row .= Element('td', Element('input', array('type' => 'submit',
277                                                       'class' => 'button',
278                                                       'value' => $button_text)));
279       }
280
281       return Element('form', $formargs,
282                      Element('table', array('cellspacing' => 0, 'cellpadding' => 2, 'border' => 0),
283                              Element('tr', $row)));
284    }
285
286    function SplitQueryArgs ($query_args = '') 
287    {
288       $split_args = split('&', $query_args);
289       $args = array();
290       while (list($key, $val) = each($split_args))
291          if (preg_match('/^ ([^=]+) =? (.*) /x', $val, $m))
292             $args[$m[1]] = $m[2];
293       return $args;
294    }
295    
296 function LinkPhpwikiURL($url, $text = '') {
297         $args = array();
298
299         if (!preg_match('/^ phpwiki: ([^?]*) [?]? (.*) $/x', $url, $m))
300             return Element('strong',
301                            QElement('u', array('class' => 'baduri'),
302                                     'BAD phpwiki: URL'));
303         if ($m[1])
304                 $pagename = urldecode($m[1]);
305         $qargs = $m[2];
306       
307         if (empty($pagename) && preg_match('/^(diff|edit|links|info)=([^&]+)$/', $qargs, $m)) {
308                 // Convert old style links (to not break diff links in RecentChanges).
309                 $pagename = urldecode($m[2]);
310                 $args = array("action" => $m[1]);
311         }
312         else {
313                 $args = SplitQueryArgs($qargs);
314         }
315
316         if (empty($pagename))
317                 $pagename = $GLOBALS['pagename'];
318
319         if (isset($args['action']) && $args['action'] == 'browse')
320                 unset($args['action']);
321         /*FIXME:
322         if (empty($args['action']))
323                 $class = 'wikilink';
324         else if (is_safe_action($args['action']))
325                 $class = 'wikiaction';
326         */
327         if (empty($args['action']) || is_safe_action($args['action']))
328                 $class = 'wikiaction';
329         else {
330                 // Don't allow administrative links on unlocked pages.
331                 // FIXME: Ugh: don't like this...
332                 global $dbi;
333                 $page = $dbi->getPage($GLOBALS['pagename']);
334                 if (!$page->get('locked'))
335                         return QElement('u', array('class' => 'wikiunsafe'),
336                                                         gettext('Lock page to enable link'));
337
338                 $class = 'wikiadmin';
339         }
340       
341         // FIXME: ug, don't like this
342         if (preg_match('/=\d*\(/', $qargs))
343                 return MakeWikiForm($pagename, $args, $class, $text);
344         if ($text)
345                 $text = htmlspecialchars($text);
346         else
347                 $text = QElement('span', array('class' => 'rawurl'), $url);
348
349         return Element('a', array('href' => WikiURL($pagename, $args),
350                                                           'class' => $class),
351                                    $text);
352 }
353
354    function ParseAndLink($bracketlink) {
355       global $dbi, $AllowedProtocols, $InlineImages;
356       global $InterWikiLinkRegexp;
357
358       // $bracketlink will start and end with brackets; in between
359       // will be either a page name, a URL or both separated by a pipe.
360
361       // strip brackets and leading space
362       preg_match("/(\[\s*)(.+?)(\s*\])/", $bracketlink, $match);
363       // match the contents 
364       preg_match("/([^|]+)(\|)?([^|]+)?/", $match[2], $matches);
365
366       if (isset($matches[3])) {
367          // named link of the form  "[some link name | http://blippy.com/]"
368          $URL = trim($matches[3]);
369          $linkname = trim($matches[1]);
370          $linktype = 'named';
371       } else {
372          // unnamed link of the form "[http://blippy.com/] or [wiki page]"
373          $URL = trim($matches[1]);
374          $linkname = '';
375          $linktype = 'simple';
376       }
377
378       if ($dbi->isWikiPage($URL)) {
379          $link['type'] = "wiki-$linktype";
380          $link['link'] = LinkExistingWikiWord($URL, $linkname);
381       } elseif (preg_match("#^($AllowedProtocols):#", $URL)) {
382         // if it's an image, embed it; otherwise, it's a regular link
383          if (preg_match("/($InlineImages)$/i", $URL)) {
384             $link['type'] = "image-$linktype";
385             $link['link'] = LinkImage($URL, $linkname);
386          } else {
387             $link['type'] = "url-$linktype";
388             $link['link'] = LinkURL($URL, $linkname);
389          }
390       } elseif (preg_match("#^phpwiki:(.*)#", $URL, $match)) {
391          $link['type'] = "url-wiki-$linktype";
392          $link['link'] = LinkPhpwikiURL($URL, $linkname);
393       } elseif (preg_match("#^\d+$#", $URL)) {
394          $link['type'] = "footnote-$linktype";
395          $link['link'] = $URL;
396       } elseif (function_exists('LinkInterWikiLink') &&
397                 preg_match("#^$InterWikiLinkRegexp:#", $URL)) {
398          $link['type'] = "interwiki-$linktype";
399          $link['link'] = LinkInterWikiLink($URL, $linkname);
400       } else {
401          $link['type'] = "wiki-unknown-$linktype";
402          $link['link'] = LinkUnknownWikiWord($URL, $linkname);
403       }
404
405       return $link;
406    }
407
408
409 function ExtractWikiPageLinks($content)
410 {
411     global $WikiNameRegexp;
412     
413     if (is_string($content))
414         $content = explode("\n", $content);
415
416     $wikilinks = array();
417     foreach ($content as $line) {
418         // remove plugin code
419         $line = preg_replace('/<\?plugin\s+\w.*?\?>/', '', $line);
420         // remove escaped '['
421         $line = str_replace('[[', ' ', $line);
422
423   // bracket links (only type wiki-* is of interest)
424   $numBracketLinks = preg_match_all("/\[\s*([^\]|]+\|)?\s*(\S.*?)\s*\]/", $line, $brktlinks);
425   for ($i = 0; $i < $numBracketLinks; $i++) {
426      $link = ParseAndLink($brktlinks[0][$i]);
427      if (preg_match("#^wiki#", $link['type']))
428         $wikilinks[$brktlinks[2][$i]] = 1;
429
430          $brktlink = preg_quote($brktlinks[0][$i]);
431          $line = preg_replace("|$brktlink|", '', $line);
432   }
433
434       // BumpyText old-style wiki links
435       if (preg_match_all("/!?$WikiNameRegexp/", $line, $link)) {
436          for ($i = 0; isset($link[0][$i]); $i++) {
437             if($link[0][$i][0] <> '!')
438                $wikilinks[$link[0][$i]] = 1;
439      }
440       }
441    }
442    return array_keys($wikilinks);
443 }      
444
445    function LinkRelatedPages($dbi, $pagename)
446    {
447       // currently not supported everywhere
448       if(!function_exists('GetWikiPageLinks'))
449          return '';
450
451       //FIXME: fix or toss?
452       $links = GetWikiPageLinks($dbi, $pagename);
453
454       $txt = QElement('strong',
455                       sprintf (gettext ("%d best incoming links:"), NUM_RELATED_PAGES));
456       for($i = 0; $i < NUM_RELATED_PAGES; $i++) {
457          if(isset($links['in'][$i])) {
458             list($name, $score) = $links['in'][$i];
459             $txt .= LinkExistingWikiWord($name) . " ($score), ";
460          }
461       }
462       
463       $txt .= "\n" . Element('br');
464       $txt .= Element('strong',
465                       sprintf (gettext ("%d best outgoing links:"), NUM_RELATED_PAGES));
466       for($i = 0; $i < NUM_RELATED_PAGES; $i++) {
467          if(isset($links['out'][$i])) {
468             list($name, $score) = $links['out'][$i];
469             if($dbi->isWikiPage($name))
470                $txt .= LinkExistingWikiWord($name) . " ($score), ";
471          }
472       }
473
474       $txt .= "\n" . Element('br');
475       $txt .= Element('strong',
476                       sprintf (gettext ("%d most popular nearby:"), NUM_RELATED_PAGES));
477       for($i = 0; $i < NUM_RELATED_PAGES; $i++) {
478          if(isset($links['popular'][$i])) {
479             list($name, $score) = $links['popular'][$i];
480             $txt .= LinkExistingWikiWord($name) . " ($score), ";
481          }
482       }
483       
484       return $txt;
485    }
486
487
488 /**
489  * Split WikiWords in page names.
490  *
491  * It has been deemed useful to split WikiWords (into "Wiki Words")
492  * in places like page titles.  This is rumored to help search engines
493  * quite a bit.
494  *
495  * @param $page string The page name.
496  *
497  * @return string The split name.
498  */
499 function split_pagename ($page) {
500     
501     if (preg_match("/\s/", $page))
502         return $page;           // Already split --- don't split any more.
503
504     // FIXME: this algorithm is Anglo-centric.
505     static $RE;
506     if (!isset($RE)) {
507         // This mess splits between a lower-case letter followed by either an upper-case
508         // or a numeral; except that it wont split the prefixes 'Mc', 'De', or 'Di' off
509         // of their tails.
510                         $RE[] = '/([[:lower:]])((?<!Mc|De|Di)[[:upper:]]|\d)/';
511         // This the single-letter words 'I' and 'A' from any following capitalized words.
512         $RE[] = '/(?: |^)([AI])([[:upper:]])/';
513         // Split numerals from following letters.
514         $RE[] = '/(\d)([[:alpha:]])/';
515
516         foreach ($RE as $key => $val)
517             $RE[$key] = pcre_fix_posix_classes($val);
518     }
519
520     foreach ($RE as $regexp)
521         $page = preg_replace($regexp, '\\1 \\2', $page);
522     return $page;
523 }
524
525 function NoSuchRevision ($page, $version) {
526     $html = Element('p', QElement('strong', gettext("Bad Version"))) . "\n";
527     $html .= QElement('p',
528                       sprintf(gettext("I'm sorry.  Version %d of %s is not in my database."),
529                               $version, $page->getName())) . "\n";
530
531     include_once('lib/Template.php');
532     echo GeneratePage('MESSAGE', $html, gettext("Bad Version"));
533     ExitWiki ("");
534 }
535
536
537 /**
538  * Get time offset for local time zone.
539  *
540  * @param $time time_t Get offset for this time.  Default: now.
541  * @param $no_colon boolean Don't put colon between hours and minutes.
542  * @return string Offset as a string in the format +HH:MM.
543  */
544 function TimezoneOffset ($time = false, $no_colon = false) {
545     if ($time === false)
546         $time = time();
547     $secs = date('Z', $time);
548     if ($secs < 0) {
549         $sign = '-';
550         $secs = -$secs;
551     }
552     else {
553         $sign = '+';
554     }
555     $colon = $no_colon ? '' : ':';
556     $mins = intval(($secs + 30) / 60);
557     return sprintf("%s%02d%s%02d",
558                    $sign, $mins / 60, $colon, $mins % 60);
559 }
560     
561 /**
562  * Format time in ISO-8601 format.
563  *
564  * @param $time time_t Time.  Default: now.
565  * @return string Date and time in ISO-8601 format.
566  */
567 function Iso8601DateTime ($time = false) {
568     if ($time === false)
569         $time = time();
570     $tzoff = TimezoneOffset($time);
571     $date = date('Y-m-d', $time);
572     $time=date('H:i:s', $time);
573     return $date . 'T' . $time . $tzoff;
574 }
575
576 /**
577  * Format time in RFC-2822 format.
578  *
579  * @param $time time_t Time.  Default: now.
580  * @return string Date and time in RFC-2822 format.
581  */
582 function Rfc2822DateTime ($time = false) {
583     if ($time === false)
584         $time = time();
585     return date('D, j M Y H:i:s ', $time) . TimezoneOffset($time, 'no colon');
586 }
587
588 /**
589  * Format time to standard 'ctime' format.
590  *
591  * @param $time time_t Time.  Default: now.
592  * @return string Date and time in RFC-2822 format.
593  */
594 function CTime ($time = false)
595 {
596     if ($time === false)
597         $time = time();
598     return date("D M j H:i:s Y", $time);
599 }
600
601 // (c-file-style: "gnu")
602 // Local Variables:
603 // mode: php
604 // tab-width: 8
605 // c-basic-offset: 4
606 // c-hanging-comment-ender-p: nil
607 // indent-tabs-mode: nil
608 // End:   
609 ?>