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