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