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