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