]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/stdlib.php
Undefined links will now have a question mark at the start instead of
[SourceForge/phpwiki.git] / lib / stdlib.php
1 <?php rcs_id('$Id: stdlib.php,v 1.41 2001-08-12 23:57:37 wainstead Exp $');
2
3
4    /*
5       Standard functions for Wiki functionality
6          WikiURL($pagename, $args, $abs)
7          
8          LinkExistingWikiWord($wikiword, $linktext) 
9          LinkUnknownWikiWord($wikiword, $linktext) 
10          LinkURL($url, $linktext)
11          LinkImage($url, $alt)
12          LinkInterWikiLink($link, $linktext)
13          CookSpaces($pagearray) 
14          class Stack (push(), pop(), cnt(), top())
15          UpdateRecentChanges($dbi, $pagename, $isnewpage) 
16          ParseAndLink($bracketlink)
17          ExtractWikiPageLinks($content)
18          LinkRelatedPages($dbi, $pagename)
19          GeneratePage($template, $content, $name, $hash)
20    */
21
22 function fix_magic_quotes_gpc (&$text)
23 {
24    if (get_magic_quotes_gpc()) {
25       $text = stripslashes($text);
26    }
27    return $text;
28 }
29
30 function arrays_equal ($a, $b) 
31 {
32    if (sizeof($a) != sizeof($b))
33       return false;
34    for ($i = 0; $i < sizeof($a); $i++)
35       if ($a[$i] != $b[$i])
36          return false;
37    return true;
38 }
39
40
41    function DataURL($url) {
42       if (preg_match('@^(\w+:|/)@', $url))
43          return $url;
44       return SERVER_URL . DATA_PATH . "/$url";
45    }
46           
47    function WikiURL($pagename, $args = '') {
48       if (is_array($args))
49       {
50          reset($args);
51          $enc_args = array();
52          while (list ($key, $val) = each($args)) {
53             $enc_args[] = urlencode($key) . '=' . urlencode($val);
54          }
55          $args = join('&', $enc_args);
56       }
57
58       if (USE_PATH_INFO) {
59          $url = rawurlencode($pagename);
60          if ($args)
61             $url .= "?$args";
62       }
63       else {
64          $url = basename(SCRIPT_NAME) .
65              "?pagename=" . rawurlencode($pagename);
66          if ($args)
67             $url .= "&$args";
68       }
69
70       return $url;
71    }
72
73 function StartTag($tag, $args = '')
74 {
75    $s = "<$tag";
76    if (is_array($args))
77    {
78       while (list($key, $val) = each($args))
79       {
80          if (is_string($val) || is_numeric($val))
81             $s .= sprintf(' %s="%s"', $key, htmlspecialchars($val));
82          else if ($val)
83             $s .= " $key";
84       }
85    }
86    return "$s>";
87 }
88
89    
90    define('NO_END_TAG_PAT',
91           '/^' . join('|', array('area', 'base', 'basefont',
92                                  'br', 'col', 'frame',
93                                  'hr', 'image', 'input',
94                                  'isindex', 'link', 'meta',
95                                  'param')) . '$/i');
96
97    function Element($tag, $args = '', $content = '')
98    {
99       $html = "<$tag";
100       if (!is_array($args))
101       {
102          $content = $args;
103          $args = false;
104       }
105       $html = StartTag($tag, $args);
106       if (!preg_match(NO_END_TAG_PAT, $tag))
107       {
108          $html .= $content;
109          $html .= "</$tag>";//FIXME: newline might not always be desired.
110       }
111       return $html;
112    }
113
114    function QElement($tag, $args = '', $content = '')
115    {
116       if (is_array($args))
117          return Element($tag, $args, htmlspecialchars($content));
118       else
119       {
120          $content = $args;
121          return Element($tag, htmlspecialchars($content));
122       }
123    }
124    
125    function LinkURL($url, $linktext='') {
126       // FIXME: Is this needed (or sufficient?)
127       if(ereg("[<>\"]", $url)) {
128          return "<b><u>BAD URL -- remove all of &lt;, &gt;, &quot;</u></b>";
129       }
130
131
132       if (empty($linktext))
133          $linktext = QElement('span', array('class' => 'rawurl'), $url);
134       else
135          $linktext = htmlspecialchars($linktext);
136
137       return Element('a',
138                      array('href' => $url, 'class' => 'linkurl'),
139                      $linktext);
140    }
141
142    function LinkExistingWikiWord($wikiword, $linktext='') {
143       if (empty($linktext))
144          $linktext = QElement('span', array('class' => 'wikiword'), $wikiword);
145       else
146          $linktext = htmlspecialchars($linktext);
147       
148       return Element('a', array('href' => WikiURL($wikiword),
149                                 'class' => 'wikilink'),
150                      $linktext);
151    }
152
153    function LinkUnknownWikiWord($wikiword, $linktext='') {
154       if (empty($linktext))
155          $linktext = QElement('span', array('class' => 'wikiword'), $wikiword);
156       else
157          $linktext = htmlspecialchars($linktext);
158
159       return Element('span', array('class' => 'wikiunknown'),
160                      QElement('a', array('href' => WikiURL($wikiword, array('action' => 'edit')),'class' => 'wikiunknown'),'?')
161 .                    Element('u', $linktext)
162 );
163    }
164
165
166    function LinkImage($url, $alt='[External Image]') {
167       // FIXME: Is this needed (or sufficient?)
168       //  As long as the src in htmlspecialchars()ed I think it's safe.
169       if(ereg('[<>"]', $url)) {
170          return "<b><u>BAD URL -- remove all of &lt;, &gt;, &quot;</u></b>";
171       }
172       return Element('img', array('src' => $url, 'alt' => $alt));
173    }
174
175
176    // converts spaces to tabs
177    function CookSpaces($pagearray) {
178       return preg_replace("/ {3,8}/", "\t", $pagearray);
179    }
180
181
182    class Stack {
183       var $items = array();
184       var $size = 0;
185
186       function push($item) {
187          $this->items[$this->size] = $item;
188          $this->size++;
189          return true;
190       }  
191    
192       function pop() {
193          if ($this->size == 0) {
194             return false; // stack is empty
195          }  
196          $this->size--;
197          return $this->items[$this->size];
198       }  
199    
200       function cnt() {
201          return $this->size;
202       }  
203
204       function top() {
205          if($this->size)
206             return $this->items[$this->size - 1];
207          else
208             return '';
209       }  
210
211    }  
212    // end class definition
213
214
215    function MakeWikiForm ($pagename, $args, $class, $button_text = '')
216    {
217       $formargs['action'] = USE_PATH_INFO ? WikiURL($pagename) : SCRIPT_NAME;
218       $formargs['method'] = 'post';
219       $formargs['class'] = $class;
220       
221       $contents = '';
222       $input_seen = 0;
223       
224       while (list($key, $val) = each($args))
225       {
226          $a = array('name' => $key, 'value' => $val, 'type' => 'hidden');
227          
228          if (preg_match('/^ (\d*) \( (.*) \) ((upload)?) $/xi', $val, $m))
229          {
230             $input_seen++;
231             $a['type'] = 'text';
232             $a['size'] = $m[1] ? $m[1] : 30;
233             $a['value'] = $m[2];
234             if ($m[3])
235             {
236                $a['type'] = 'file';
237                $formargs['enctype'] = 'multipart/form-data';
238                $contents .= Element('input',
239                                     array('name' => 'MAX_FILE_SIZE',
240                                           'value' => MAX_UPLOAD_SIZE,
241                                           'type' => 'hidden'));
242             }
243          }
244
245          $contents .= Element('input', $a);
246       }
247
248       $row = Element('td', $contents);
249       
250       if (!empty($button_text)) {
251          $row .= Element('td', Element('input', array('type' => 'submit',
252                                                       'value' => $button_text)));
253       }
254
255       return Element('form', $formargs,
256                      Element('table', array('cellspacing' => 0, 'cellpadding' => 2, 'border' => 0),
257                              Element('tr', $row)));
258    }
259
260    function SplitQueryArgs ($query_args = '') 
261    {
262       $split_args = split('&', $query_args);
263       $args = array();
264       while (list($key, $val) = each($split_args))
265          if (preg_match('/^ ([^=]+) =? (.*) /x', $val, $m))
266             $args[$m[1]] = $m[2];
267       return $args;
268    }
269    
270    function LinkPhpwikiURL($url, $text = '') {
271       global $pagename;
272       $args = array();
273       $page = $pagename;
274
275       if (!preg_match('/^ phpwiki: ([^?]*) [?]? (.*) $/x', $url, $m))
276          return "<b><u>BAD phpwiki: URL</u></b>";
277
278       if ($m[1])
279          $page = urldecode($m[1]);
280       $qargs = $m[2];
281       
282       if (!$page && preg_match('/^(diff|edit|links|info|diff)=([^&]+)$/', $qargs, $m))
283       {
284          // Convert old style links (to not break diff links in RecentChanges).
285          $page = urldecode($m[2]);
286          $args = array("action" => $m[1]);
287       }
288       else
289       {
290          $args = SplitQueryArgs($qargs);
291       }
292
293       if (isset($args['action']) && $args['action'] == 'browse')
294          unset($args['action']);
295
296       if (empty($args['action']))
297          $class = 'wikilink';
298       else if (IsSafeAction($args['action']))
299          $class = 'wikiaction';
300       else
301       {
302          // Don't allow administrative links on unlocked pages.
303          // FIXME: Ugh: don't like this...
304          global $pagehash;
305          if (($pagehash['flags'] & FLAG_PAGE_LOCKED) == 0)
306             return QElement('u', array('class' => 'wikiunsafe'),
307                             gettext('Lock page to enable link'));
308
309          $class = 'wikiadmin';
310       }
311       
312       // FIXME: ug, don't like this
313       if (preg_match('/=\d*\(/', $qargs))
314          return MakeWikiForm($page, $args, $class, $text);
315       else
316       {
317          if ($text)
318             $text = htmlspecialchars($text);
319          else
320             $text = QElement('span', array('class' => 'rawurl'), $url);
321                              
322          return Element('a', array('href' => WikiURL($page, $args),
323                                    'class' => $class),
324                         $text);
325       }
326    }
327
328    function ParseAndLink($bracketlink) {
329       global $dbi, $AllowedProtocols, $InlineImages;
330       global $InterWikiLinkRegexp;
331
332       // $bracketlink will start and end with brackets; in between
333       // will be either a page name, a URL or both separated by a pipe.
334
335       // strip brackets and leading space
336       preg_match("/(\[\s*)(.+?)(\s*\])/", $bracketlink, $match);
337       // match the contents 
338       preg_match("/([^|]+)(\|)?([^|]+)?/", $match[2], $matches);
339
340       if (isset($matches[3])) {
341          // named link of the form  "[some link name | http://blippy.com/]"
342          $URL = trim($matches[3]);
343          $linkname = trim($matches[1]);
344          $linktype = 'named';
345       } else {
346          // unnamed link of the form "[http://blippy.com/] or [wiki page]"
347          $URL = trim($matches[1]);
348          $linkname = '';
349          $linktype = 'simple';
350       }
351
352       if (IsWikiPage($dbi, $URL)) {
353          $link['type'] = "wiki-$linktype";
354          $link['link'] = LinkExistingWikiWord($URL, $linkname);
355       } elseif (preg_match("#^($AllowedProtocols):#", $URL)) {
356         // if it's an image, embed it; otherwise, it's a regular link
357          if (preg_match("/($InlineImages)$/i", $URL)) {
358             $link['type'] = "image-$linktype";
359             $link['link'] = LinkImage($URL, $linkname);
360          } else {
361             $link['type'] = "url-$linktype";
362             $link['link'] = LinkURL($URL, $linkname);
363          }
364       } elseif (preg_match("#^phpwiki:(.*)#", $URL, $match)) {
365          $link['type'] = "url-wiki-$linktype";
366          $link['link'] = LinkPhpwikiURL($URL, $linkname);
367       } elseif (preg_match("#^\d+$#", $URL)) {
368          $link['type'] = "footnote-$linktype";
369          $link['link'] = $URL;
370       } elseif (function_exists('LinkInterWikiLink') &&
371                 preg_match("#^$InterWikiLinkRegexp:#", $URL)) {
372          $link['type'] = "interwiki-$linktype";
373          $link['link'] = LinkInterWikiLink($URL, $linkname);
374       } else {
375          $link['type'] = "wiki-unknown-$linktype";
376          $link['link'] = LinkUnknownWikiWord($URL, $linkname);
377       }
378
379       return $link;
380    }
381
382
383    function ExtractWikiPageLinks($content)
384    {
385       global $WikiNameRegexp;
386
387       $wikilinks = array();
388       $numlines = count($content);
389       for($l = 0; $l < $numlines; $l++)
390       {
391          // remove escaped '['
392          $line = str_replace('[[', ' ', $content[$l]);
393
394          // bracket links (only type wiki-* is of interest)
395          $numBracketLinks = preg_match_all("/\[\s*([^\]|]+\|)?\s*(.+?)\s*\]/", $line, $brktlinks);
396          for ($i = 0; $i < $numBracketLinks; $i++) {
397             $link = ParseAndLink($brktlinks[0][$i]);
398             if (preg_match("#^wiki#", $link['type']))
399                $wikilinks[$brktlinks[2][$i]] = 1;
400
401             $brktlink = preg_quote($brktlinks[0][$i]);
402             $line = preg_replace("|$brktlink|", '', $line);
403          }
404
405          // BumpyText old-style wiki links
406          if (preg_match_all("/!?$WikiNameRegexp/", $line, $link)) {
407             for ($i = 0; isset($link[0][$i]); $i++) {
408                if($link[0][$i][0] <> '!')
409                   $wikilinks[$link[0][$i]] = 1;
410             }
411          }
412       }
413       return $wikilinks;
414    }      
415
416    function LinkRelatedPages($dbi, $pagename)
417    {
418       // currently not supported everywhere
419       if(!function_exists('GetWikiPageLinks'))
420          return '';
421
422       $links = GetWikiPageLinks($dbi, $pagename);
423
424       $txt = "<b>";
425       $txt .= sprintf (gettext ("%d best incoming links:"), NUM_RELATED_PAGES);
426       $txt .= "</b>\n";
427       for($i = 0; $i < NUM_RELATED_PAGES; $i++) {
428          if(isset($links['in'][$i])) {
429             list($name, $score) = $links['in'][$i];
430             $txt .= LinkExistingWikiWord($name) . " ($score), ";
431          }
432       }
433
434       $txt .= "\n<br><b>";
435       $txt .= sprintf (gettext ("%d best outgoing links:"), NUM_RELATED_PAGES);
436       $txt .= "</b>\n";
437       for($i = 0; $i < NUM_RELATED_PAGES; $i++) {
438          if(isset($links['out'][$i])) {
439             list($name, $score) = $links['out'][$i];
440             if(IsWikiPage($dbi, $name))
441                $txt .= LinkExistingWikiWord($name) . " ($score), ";
442          }
443       }
444
445       $txt .= "\n<br><b>";
446       $txt .= sprintf (gettext ("%d most popular nearby:"), NUM_RELATED_PAGES);
447       $txt .= "</b>\n";
448       for($i = 0; $i < NUM_RELATED_PAGES; $i++) {
449          if(isset($links['popular'][$i])) {
450             list($name, $score) = $links['popular'][$i];
451             $txt .= LinkExistingWikiWord($name) . " ($score), ";
452          }
453       }
454       
455       return $txt;
456    }
457
458    
459    # GeneratePage() -- takes $content and puts it in the template $template
460    # this function contains all the template logic
461    #
462    # $template ... name of the template (see config.php for list of names)
463    # $content ... html content to put into the page
464    # $name ... page title
465    # $hash ... if called while creating a wiki page, $hash points to
466    #           the $pagehash array of that wiki page.
467
468    function GeneratePage($template, $content, $name, $hash)
469    {
470       global $templates;
471       global $datetimeformat, $dbi, $logo, $FieldSeparator;
472       global $user, $pagename;
473                 global $WikiPageStore;
474       
475       if (!is_array($hash))
476          unset($hash);
477
478       function _dotoken ($id, $val, &$page) {
479          global $FieldSeparator;
480          $page = str_replace("$FieldSeparator#$id$FieldSeparator#",
481                                 $val, $page);
482       }
483
484       function _iftoken ($id, $condition, &$page) {
485          global $FieldSeparator;
486
487          // line based IF directive
488          $lineyes = "$FieldSeparator#IF $id$FieldSeparator#";
489          $lineno = "$FieldSeparator#IF !$id$FieldSeparator#";
490          // block based IF directive
491          $blockyes = "$FieldSeparator#IF:$id$FieldSeparator#";
492          $blockyesend = "$FieldSeparator#ENDIF:$id$FieldSeparator#";
493          $blockno = "$FieldSeparator#IF:!$id$FieldSeparator#";
494          $blocknoend = "$FieldSeparator#ENDIF:!$id$FieldSeparator#";
495
496          if ($condition) {
497             $page = str_replace($lineyes, '', $page);
498             $page = str_replace($blockyes, '', $page);
499             $page = str_replace($blockyesend, '', $page);
500             $page = preg_replace("/$blockno(.*?)$blocknoend/s", '', $page);
501             $page = ereg_replace("${lineno}[^\n]*\n", '', $page);
502          } else {
503             $page = str_replace($lineno, '', $page);
504             $page = str_replace($blockno, '', $page);
505             $page = str_replace($blocknoend, '', $page);
506             $page = preg_replace("/$blockyes(.*?)$blockyesend/s", '', $page);
507             $page = ereg_replace("${lineyes}[^\n]*\n", '', $page);
508          }
509       }
510
511       $page = join('', file(FindLocalizedFile($templates[$template])));
512       $page = str_replace('###', "$FieldSeparator#", $page);
513
514       // valid for all pagetypes
515       _iftoken('COPY', isset($hash['copy']), $page);
516       _iftoken('LOCK',  (isset($hash['flags']) &&
517                         ($hash['flags'] & FLAG_PAGE_LOCKED)), $page);
518       _iftoken('ADMIN', $user->is_admin(), $page);
519       _iftoken('ANONYMOUS', !$user->is_authenticated(), $page);
520                 _iftoken('CURRENT', isset($hash['version']) && $hash['version'] == GetMaxVersionNumber($dbi, $hash['pagename'], $WikiPageStore), $page);
521
522       if (empty($hash['minor_edit_checkbox']))
523           $hash['minor_edit_checkbox'] = '';
524       _iftoken('MINOR_EDIT_CHECKBOX', $hash['minor_edit_checkbox'], $page);
525       
526       _dotoken('MINOR_EDIT_CHECKBOX', $hash['minor_edit_checkbox'], $page);
527
528       _dotoken('USERID', htmlspecialchars($user->id()), $page);
529       _dotoken('PAGE', htmlspecialchars($name), $page);
530       _dotoken('SPLIT_PAGE',
531                htmlspecialchars(
532                   preg_replace('/([[:lower:]])([[:upper:]])/', '\\1 \\2', $name)),
533                $page);
534       _dotoken('LOGO', htmlspecialchars(DataURL($logo)), $page);
535       _dotoken('CSS_URL', htmlspecialchars(DataURL(CSS_URL)), $page);
536
537       _dotoken('RCS_IDS', $GLOBALS['RCS_IDS'], $page);
538
539       $prefs = $user->getPreferences();
540       _dotoken('EDIT_AREA_WIDTH', $prefs['edit_area.width'], $page);
541       _dotoken('EDIT_AREA_HEIGHT', $prefs['edit_area.height'], $page);
542
543       // FIXME: Clean up this stuff
544       _dotoken('BROWSE', WikiURL(''), $page);
545
546       if (USE_PATH_INFO)
547          _dotoken('BASE_URL',
548                   SERVER_URL . VIRTUAL_PATH . "/" . WikiURL($pagename), $page);
549       else
550          _dotoken('BASE_URL', SERVER_URL . SCRIPT_NAME, $page);
551
552       if ($GLOBALS['action'] != 'browse')
553          _dotoken('ROBOTS_META',
554                   Element('meta', array('name' => 'robots',
555                                         'content' => 'noindex, nofollow')),
556                   $page);
557       else
558          _dotoken('ROBOTS_META', '', $page);
559       
560       
561       // invalid for messages (search results, error messages)
562       if ($template != 'MESSAGE') {
563          $browse_page = WikiURL($name);
564          _dotoken('BROWSE_PAGE', $browse_page, $page);
565
566          $arg_sep = strstr($browse_page, '?') ? '&amp;' : '?';
567          _dotoken('ACTION', $browse_page . $arg_sep . "action=", $page);
568
569          _dotoken('PAGEURL', rawurlencode($name), $page);
570          if (!empty($hash['lastmodified']))
571             _dotoken('LASTMODIFIED',
572                      strftime($datetimeformat, $hash['lastmodified']), $page);
573          if (!empty($hash['author']))
574             _dotoken('LASTAUTHOR', $hash['author'], $page);
575          if (!empty($hash['version']))
576             _dotoken('VERSION', $hash['version'], $page);
577          if (!empty($hash['pagename']))
578        _dotoken('CURRENT_VERSION', GetMaxVersionNumber($dbi, $hash['pagename'], $WikiPageStore), $page);
579          if (strstr($page, "$FieldSeparator#HITS$FieldSeparator#")) {
580             _dotoken('HITS', GetHitCount($dbi, $name), $page);
581          }
582          if (strstr($page, "$FieldSeparator#RELATEDPAGES$FieldSeparator#")) {
583             _dotoken('RELATEDPAGES', LinkRelatedPages($dbi, $name), $page);
584          }
585       }
586
587       _dotoken('CONTENT', $content, $page);
588       return $page;
589    }
590
591 function UpdateRecentChanges($dbi, $pagename, $isnewpage)
592 {
593    global $user;
594    global $dateformat;
595    global $WikiPageStore;
596
597    $recentchanges = RetrievePage($dbi, gettext ("RecentChanges"), $WikiPageStore, 0);
598
599    // this shouldn't be necessary, since PhpWiki loads 
600    // default pages if this is a new baby Wiki
601    $now = time();
602    $today = strftime($dateformat, $now);
603
604    if (is_array($recentchanges)) {
605       $isNewDay = strftime($dateformat, $recentchanges['lastmodified']) != $today;
606    }
607    else {
608       $recentchanges = array('version' => 1,
609                              'created' => $now,
610                              'flags' => FLAG_PAGE_LOCKED,
611                              'author' => $GLOBALS['user']->id());
612       $recentchanges['content']
613           = array(gettext("The most recently changed pages are listed below."),
614                   '',
615                   "____$today " . gettext("(first day for this Wiki)"),
616                   '',
617                   gettext("Quick title search:"),
618                   '[phpwiki:?action=search&searchterm=()]',
619                   '----');
620       $isNewDay = 0;
621    }
622    $recentchanges['lastmodified'] = $now;
623
624    $numlines = sizeof($recentchanges['content']);
625    $newpage = array();
626    $k = 0;
627
628    // scroll through the page to the first date and break
629    // dates are marked with "____" at the beginning of the line
630    for ($i = 0; $i < $numlines; $i++) {
631       if (preg_match("/^____/",
632                      $recentchanges['content'][$i])) {
633          break;
634       } else {
635          $newpage[$k++] = $recentchanges['content'][$i];
636       }
637    }
638
639    // if it's a new date, insert it
640    $newpage[$k++] = $isNewDay ? "____$today\r"
641                               : $recentchanges['content'][$i++];
642
643    $userid = $user->id();
644
645    // add the updated page's name to the array
646    if($isnewpage) {
647       $newpage[$k++] = "* [$pagename] (new) ..... $userid\r";
648    } else {
649       $diffurl = "phpwiki:" . rawurlencode($pagename) . "?action=diff";
650       $newpage[$k++] = "* [$pagename] ([diff|$diffurl]) ..... $userid\r";
651    }
652    if ($isNewDay)
653       $newpage[$k++] = "\r";
654
655    // copy the rest of the page into the new array
656    // and skip previous entry for $pagename
657    $pagename = preg_quote($pagename);
658    for (; $i < $numlines; $i++) {
659       if (!preg_match("|\[$pagename\]|", $recentchanges['content'][$i])) {
660          $newpage[$k++] = $recentchanges['content'][$i];
661       }
662    }
663
664    $recentchanges['content'] = $newpage;
665
666    ReplaceCurrentPage(gettext ("RecentChanges"), $recentchanges);
667 }
668
669 // For emacs users
670 // Local Variables:
671 // mode: php
672 // c-file-style: "ellemtel"
673 // End:   
674 ?>