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