]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/stdlib.php
Allow more pagename chars: Limit only on certain backends.
[SourceForge/phpwiki.git] / lib / stdlib.php
1 <?php //rcs_id('$Id: stdlib.php,v 1.228 2005-01-17 20:28:30 rurban Exp $');
2
3 /*
4   Standard functions for Wiki functionality
5     WikiURL ($pagename, $args, $get_abs_url)
6     AbsoluteURL ($url)
7     IconForLink ($protocol_or_url)
8     PossiblyGlueIconToText($proto_or_url, $text)
9     IsSafeURL($url)
10     LinkURL ($url, $linktext)
11     LinkImage ($url, $alt)
12
13     SplitQueryArgs ($query_args)
14     LinkPhpwikiURL ($url, $text, $basepage)
15     ConvertOldMarkup ($content, $markup_type = "block")
16     MangleXmlIdentifier($str)
17     UnMangleXmlIdentifier($str)
18     
19     class Stack { push($item), pop(), cnt(), top() }
20     class Alert { show() }
21     class WikiPageName {getParent(),isValid(),getWarnings() }
22
23     expand_tabs($str, $tab_width = 8)
24     SplitPagename ($page)
25     NoSuchRevision ($request, $page, $version)
26     TimezoneOffset ($time, $no_colon)
27     Iso8601DateTime ($time)
28     Rfc2822DateTime ($time)
29     ParseRfc1123DateTime ($timestr)
30     CTime ($time)
31     ByteFormatter ($bytes = 0, $longformat = false)
32     __printf ($fmt)
33     __sprintf ($fmt)
34     __vsprintf ($fmt, $args)
35
36     file_mtime ($filename)
37     sort_file_mtime ($a, $b)
38     class fileSet {fileSet($directory, $filepattern = false), getFiles($exclude=false, $sortby=false, $limit=false) }
39     class ListRegexExpand { listMatchCallback($item, $key),  expandRegex ($index, &$pages) }
40
41     glob_to_pcre ($glob)
42     glob_match ($glob, $against, $case_sensitive = true)
43     explodeList ($input, $allnames, $glob_style = true, $case_sensitive = true)
44     explodePageList ($input, $perm = false)
45     isa ($object, $class)
46     can ($object, $method)
47     function_usable ($function_name)
48     hash ($x)
49     better_srand ($seed = '')
50     count_all ($arg)
51     isSubPage ($pagename)
52     subPageSlice ($pagename, $pos)
53
54     phpwiki_version ()
55     isWikiWord ($word)
56     obj2hash ($obj, $exclude = false, $fields = false)
57     isUtf8String ($s)
58     fixTitleEncoding ($s)
59     url_get_contents ($uri)
60     GenerateId ($name)
61     firstNWordsOfContent ($n, $content)
62     extractSection ($section, $content, $page, $quiet = false, $sectionhead = false)
63     isExternalReferrer()
64
65   function: LinkInterWikiLink($link, $linktext)
66   moved to: lib/interwiki.php
67   function: linkExistingWikiWord($wikiword, $linktext, $version)
68   moved to: lib/Theme.php
69   function: LinkUnknownWikiWord($wikiword, $linktext)
70   moved to: lib/Theme.php
71   function: UpdateRecentChanges($dbi, $pagename, $isnewpage) 
72   gone see: lib/plugin/RecentChanges.php
73 */
74 if (defined('_PHPWIKI_STDLIB_LOADED')) return;
75 else define('_PHPWIKI_STDLIB_LOADED', true);
76
77 define('MAX_PAGENAME_LENGTH', 100);
78             
79 /**
80  * Convert string to a valid XML identifier.
81  *
82  * XML 1.0 identifiers are of the form: [A-Za-z][A-Za-z0-9:_.-]*
83  *
84  * We would like to have, e.g. named anchors within wiki pages
85  * names like "Table of Contents" --- clearly not a valid XML
86  * fragment identifier.
87  *
88  * This function implements a one-to-one map from {any string}
89  * to {valid XML identifiers}.
90  *
91  * It does this by
92  * converting all bytes not in [A-Za-z0-9:_-],
93  * and any leading byte not in [A-Za-z] to 'xbb.',
94  * where 'bb' is the hexadecimal representation of the
95  * character.
96  *
97  * As a special case, the empty string is converted to 'empty.'
98  *
99  * @param string $str
100  * @return string
101  */
102 function MangleXmlIdentifier($str) {
103     if (!$str)
104         return 'empty.';
105     
106     return preg_replace('/[^-_:A-Za-z0-9]|(?<=^)[^A-Za-z]/e',
107                         "'x' . sprintf('%02x', ord('\\0')) . '.'",
108                         $str);
109 }
110
111 function UnMangleXmlIdentifier($str) {
112     if ($str == 'empty.')
113         return '';
114     return preg_replace('/x(\w\w)\./e',
115                         "sprintf('%c', hex('\\0'))",
116                         $str);
117 }
118
119 /**
120  * Generates a valid URL for a given Wiki pagename.
121  * @param mixed $pagename If a string this will be the name of the Wiki page to link to.
122  *                        If a WikiDB_Page object function will extract the name to link to.
123  *                        If a WikiDB_PageRevision object function will extract the name to link to.
124  * @param array $args 
125  * @param boolean $get_abs_url Default value is false.
126  * @return string The absolute URL to the page passed as $pagename.
127  */
128 function WikiURL($pagename, $args = '', $get_abs_url = false) {
129     $anchor = false;
130     
131     if (is_object($pagename)) {
132         if (isa($pagename, 'WikiDB_Page')) {
133             $pagename = $pagename->getName();
134         }
135         elseif (isa($pagename, 'WikiDB_PageRevision')) {
136             $page = $pagename->getPage();
137             $args['version'] = $pagename->getVersion();
138             $pagename = $page->getName();
139         }
140         elseif (isa($pagename, 'WikiPageName')) {
141             $anchor = $pagename->anchor;
142             $pagename = $pagename->name;
143         } else { // php5
144             $anchor = $pagename->anchor;
145             $pagename = $pagename->name;
146         }
147     }
148     if (!$get_abs_url and DEBUG and $GLOBALS['request']->getArg('start_debug')) {
149         if (!$args)
150             $args = 'start_debug=' . $GLOBALS['request']->getArg('start_debug');
151         elseif (is_array($args))
152             $args['start_debug'] = $GLOBALS['request']->getArg('start_debug');
153         else 
154             $args .= '&start_debug=' . $GLOBALS['request']->getArg('start_debug');
155     }
156     if (is_array($args)) {
157         $enc_args = array();
158         foreach ($args as $key => $val) {
159             // avoid default args
160             if (USE_PATH_INFO and $key == 'pagename')
161                 ; 
162             elseif ($key == 'action' and $val == 'browse')
163                 ;
164             elseif (!is_array($val)) // ugly hack for getURLtoSelf() which also takes POST vars
165               $enc_args[] = urlencode($key) . '=' . urlencode($val);
166         }
167         $args = join('&', $enc_args);
168     }
169
170     if (USE_PATH_INFO or !empty($GLOBALS['WikiTheme']->HTML_DUMP_SUFFIX)) {
171         $url = $get_abs_url ? (SERVER_URL . VIRTUAL_PATH . "/") : "";
172         $url = $url . preg_replace('/%2f/i', '/', rawurlencode($pagename));
173         if (!empty($GLOBALS['WikiTheme']->HTML_DUMP_SUFFIX))
174             $url .= $GLOBALS['WikiTheme']->HTML_DUMP_SUFFIX;
175         if ($args)
176             $url .= "?$args";
177     }
178     else {
179         $url = $get_abs_url ? SERVER_URL . SCRIPT_NAME : basename(SCRIPT_NAME);
180         $url .= "?pagename=" . rawurlencode($pagename);
181         if ($args)
182             $url .= "&$args";
183     }
184     if ($anchor)
185         $url .= "#" . MangleXmlIdentifier($anchor);
186     return $url;
187 }
188
189 /** Convert relative URL to absolute URL.
190  *
191  * This converts a relative URL to one of PhpWiki's support files
192  * to an absolute one.
193  *
194  * @param string $url
195  * @return string Absolute URL
196  */
197 function AbsoluteURL ($url) {
198     if (preg_match('/^https?:/', $url))
199         return $url;
200     if ($url[0] != '/') {
201         $base = USE_PATH_INFO ? VIRTUAL_PATH : dirname(SCRIPT_NAME);
202         while ($base != '/' and substr($url, 0, 3) == "../") {
203             $url = substr($url, 3);
204             $base = dirname($base);
205         }
206         if ($base != '/')
207             $base .= '/';
208         $url = $base . $url;
209     }
210     return SERVER_URL . $url;
211 }
212
213 function DataURL ($url) {
214     if (preg_match('/^https?:/', $url))
215         return $url;
216     $url = NormalizeWebFileName($url);
217     if (DEBUG and $GLOBALS['request']->getArg('start_debug') and substr($url,-4,4) == '.php')
218         $url .= "?start_debug=1"; // XMLRPC and SOAP debugging helper.
219     return AbsoluteURL($url);
220 }
221
222 /**
223  * Generates icon in front of links.
224  *
225  * @param string $protocol_or_url URL or protocol to determine which icon to use.
226  *
227  * @return HtmlElement HtmlElement object that contains data to create img link to
228  * icon for use with url or protocol passed to the function. False if no img to be
229  * displayed.
230  */
231 function IconForLink($protocol_or_url) {
232     global $WikiTheme;
233     if (0 and $filename_suffix == false) {
234         // display apache style icon for file type instead of protocol icon
235         // - archive: unix:gz,bz2,tgz,tar,z; mac:dmg,dmgz,bin,img,cpt,sit; pc:zip;
236         // - document: html, htm, text, txt, rtf, pdf, doc
237         // - non-inlined image: jpg,jpeg,png,gif,tiff,tif,swf,pict,psd,eps,ps
238         // - audio: mp3,mp2,aiff,aif,au
239         // - multimedia: mpeg,mpg,mov,qt
240     } else {
241         list ($proto) = explode(':', $protocol_or_url, 2);
242         $src = $WikiTheme->getLinkIconURL($proto);
243         if ($src)
244             return HTML::img(array('src' => $src, 'alt' => "", 'class' => 'linkicon', 'border' => 0));
245         else
246             return false;
247     }
248 }
249
250 /**
251  * Glue icon in front of or after text.
252  * Pref: 'noLinkIcons'  - ignore icon if set
253  * Theme: 'LinkIcons'   - 'yes'   at front
254  *                      - 'no'    display no icon
255  *                      - 'front' display at left
256  *                      - 'after' display at right
257  *
258  * @param string $protocol_or_url Protocol or URL.  Used to determine the
259  * proper icon.
260  * @param string $text The text.
261  * @return XmlContent.
262  */
263 function PossiblyGlueIconToText($proto_or_url, $text) {
264     global $request, $WikiTheme;
265     if ($request->getPref('noLinkIcons'))
266         return $text;
267     $icon = IconForLink($proto_or_url);
268     if (!$icon)
269         return $text;
270     if ($where = $WikiTheme->getLinkIconAttr()) {
271         if ($where == 'no') return $text;
272         if ($where != 'after') $where = 'front';
273     } else {
274         $where = 'front';
275     }
276     if ($where == 'after') {
277         // span the icon only to the last word (tie them together), 
278         // to let the previous words wrap on line breaks.
279         if (!is_object($text)) {
280             preg_match('/^(\s*\S*)(\s*)$/', $text, $m);
281             list (, $prefix, $last_word) = $m;
282         }
283         else {
284             $last_word = $text;
285             $prefix = false;
286         }
287         $text = HTML::span(array('style' => 'white-space: nowrap'),
288                            $last_word, HTML::Raw('&nbsp;'), $icon);
289         if ($prefix)
290             $text = HTML($prefix, $text);
291         return $text;
292     }
293     // span the icon only to the first word (tie them together), 
294     // to let the next words wrap on line breaks
295     if (!is_object($text)) {
296         preg_match('/^\s*(\S*)(.*?)\s*$/', $text, $m);
297         list (, $first_word, $tail) = $m;
298     }
299     else {
300         $first_word = $text;
301         $tail = false;
302     }
303     $text = HTML::span(array('style' => 'white-space: nowrap'),
304                        $icon, $first_word);
305     if ($tail)
306         $text = HTML($text, $tail);
307     return $text;
308 }
309
310 /**
311  * Determines if the url passed to function is safe, by detecting if the characters
312  * '<', '>', or '"' are present.
313  *
314  * @param string $url URL to check for unsafe characters.
315  * @return boolean True if same, false else.
316  */
317 function IsSafeURL($url) {
318     return !preg_match('/[<>"]/', $url);
319 }
320
321 /**
322  * Generates an HtmlElement object to store data for a link.
323  *
324  * @param string $url URL that the link will point to.
325  * @param string $linktext Text to be displayed as link.
326  * @return HtmlElement HtmlElement object that contains data to construct an html link.
327  */
328 function LinkURL($url, $linktext = '') {
329     // FIXME: Is this needed (or sufficient?)
330     if(! IsSafeURL($url)) {
331         $link = HTML::strong(HTML::u(array('class' => 'baduri'),
332                                      _("BAD URL -- remove all of <, >, \"")));
333     }
334     else {
335         if (!$linktext)
336             $linktext = preg_replace("/mailto:/A", "", $url);
337         
338         $link = HTML::a(array('href' => $url),
339                         PossiblyGlueIconToText($url, $linktext));
340         
341     }
342     $link->setAttr('class', $linktext ? 'namedurl' : 'rawurl');
343     return $link;
344 }
345
346 /**
347  * FIXME: disallow sizes which are too small. 
348  * Spammers may use such (typically invisible) image attributes to higher their GoogleRank.
349  */
350 function LinkImage($url, $alt = false) {
351     // FIXME: Is this needed (or sufficient?)
352     if(! IsSafeURL($url)) {
353         $link = HTML::strong(HTML::u(array('class' => 'baduri'),
354                                      _("BAD URL -- remove all of <, >, \"")));
355     } else {
356         // support new syntax: [image.jpg size=50% border=n]
357         $arr = split(' ',$url);
358         if (count($arr) > 1) {
359             $url = $arr[0];
360         }
361         if (empty($alt)) $alt = basename($url);
362         $link = HTML::img(array('src' => $url, 'alt' => $alt));
363         if (count($arr) > 1) {
364             array_shift($arr);
365             foreach ($arr as $attr) {
366                 if (preg_match('/^size=(\d+%)$/',$attr,$m)) {
367                     $link->setAttr('width',$m[1]);
368                     $link->setAttr('height',$m[1]);
369                 }
370                 if (preg_match('/^size=(\d+)x(\d+)$/',$attr,$m)) {
371                     $link->setAttr('width',$m[1]);
372                     $link->setAttr('height',$m[2]);
373                 }
374                 if (preg_match('/^border=(\d+)$/',$attr,$m))
375                     $link->setAttr('border',$m[1]);
376                 if (preg_match('/^align=(\w+)$/',$attr,$m))
377                     $link->setAttr('align',$m[1]);
378                 if (preg_match('/^hspace=(\d+)$/',$attr,$m))
379                     $link->setAttr('hspace',$m[1]);
380                 if (preg_match('/^vspace=(\d+)$/',$attr,$m))
381                     $link->setAttr('vspace',$m[1]);
382             }
383         }
384         // check width and height as spam countermeasure
385         if (($width  = $link->getAttr('width')) and ($height = $link->getAttr('height'))) {
386             //$width  = (int) $width; // px or % or other suffix
387             //$height = (int) $height;
388             if (($width < 3 and $height < 10) or 
389                 ($height < 3 and $width < 20) or 
390                 ($height < 7 and $width < 7))
391             {
392                 trigger_error(_("Invalid image size"), E_USER_NOTICE);
393                 return '';
394             }
395         } else {
396             // Older php versions crash here with certain png's: 
397             // confirmed for 4.1.2, 4.1.3, 4.2.3; 4.3.2 and 4.3.7 are ok
398             //   http://phpwiki.sourceforge.net/demo/themes/default/images/http.png
399             // See http://bugs.php.net/search.php?cmd=display&search_for=getimagesize
400             if (!check_php_version(4,3) and preg_match("/^http.+\.png$/i",$url))
401                 ; // it's safe to assume that this will fail.
402             elseif (!DISABLE_GETIMAGESIZE and ($size = @getimagesize($url))) {
403                 $width  = $size[0];
404                 $height = $size[1];
405                 if (($width < 3 and $height < 10) 
406                     or ($height < 3 and $width < 20)
407                     or ($height < 7 and $width < 7))
408                 {
409                     trigger_error(_("Invalid image size"), E_USER_NOTICE);
410                     return '';
411                 }
412             }
413         }
414     }
415     $link->setAttr('class', 'inlineimage');
416     return $link;
417 }
418
419
420
421 class Stack {
422
423     // var in php5 deprecated
424     function Stack() {
425         $this->items = array();
426         $this->size = 0;
427     }
428     function push($item) {
429         $this->items[$this->size] = $item;
430         $this->size++;
431         return true;
432     }  
433     
434     function pop() {
435         if ($this->size == 0) {
436             return false; // stack is empty
437         }  
438         $this->size--;
439         return $this->items[$this->size];
440     }  
441     
442     function cnt() {
443         return $this->size;
444     }  
445     
446     function top() {
447         if($this->size)
448             return $this->items[$this->size - 1];
449         else
450             return '';
451     }
452     
453 }  
454 // end class definition
455
456 function SplitQueryArgs ($query_args = '') 
457 {
458     $split_args = split('&', $query_args);
459     $args = array();
460     while (list($key, $val) = each($split_args))
461         if (preg_match('/^ ([^=]+) =? (.*) /x', $val, $m))
462             $args[$m[1]] = $m[2];
463     return $args;
464 }
465
466 function LinkPhpwikiURL($url, $text = '', $basepage = false) {
467     $args = array();
468     
469     if (!preg_match('/^ phpwiki: ([^?]*) [?]? (.*) $/x', $url, $m)) {
470         return HTML::strong(array('class' => 'rawurl'),
471                             HTML::u(array('class' => 'baduri'),
472                                     _("BAD phpwiki: URL")));
473     }
474
475     if ($m[1])
476         $pagename = urldecode($m[1]);
477     $qargs = $m[2];
478     
479     if (empty($pagename) &&
480         preg_match('/^(diff|edit|links|info)=([^&]+)$/', $qargs, $m)) {
481         // Convert old style links (to not break diff links in
482         // RecentChanges).
483         $pagename = urldecode($m[2]);
484         $args = array("action" => $m[1]);
485     }
486     else {
487         $args = SplitQueryArgs($qargs);
488     }
489
490     if (empty($pagename))
491         $pagename = $GLOBALS['request']->getArg('pagename');
492
493     if (isset($args['action']) && $args['action'] == 'browse')
494         unset($args['action']);
495     
496     /*FIXME:
497       if (empty($args['action']))
498       $class = 'wikilink';
499       else if (is_safe_action($args['action']))
500       $class = 'wikiaction';
501     */
502     if (empty($args['action']) || is_safe_action($args['action']))
503         $class = 'wikiaction';
504     else {
505         // Don't allow administrative links on unlocked pages.
506         $dbi = $GLOBALS['request']->getDbh();
507         $page = $dbi->getPage($basepage ? $basepage : $pagename);
508         if (!$page->get('locked'))
509             return HTML::span(array('class' => 'wikiunsafe'),
510                               HTML::u(_("Lock page to enable link")));
511         $class = 'wikiadmin';
512     }
513     
514     if (!$text)
515         $text = HTML::span(array('class' => 'rawurl'), $url);
516
517     $wikipage = new WikiPageName($pagename);
518     if (!$wikipage->isValid()) {
519         global $WikiTheme;
520         return $WikiTheme->linkBadWikiWord($wikipage, $url);
521     }
522     
523     return HTML::a(array('href'  => WikiURL($pagename, $args),
524                          'class' => $class),
525                    $text);
526 }
527
528 /**
529  * A class to assist in parsing wiki pagenames.
530  *
531  * Now with subpages and anchors, parsing and passing around
532  * pagenames is more complicated.  This should help.
533  */
534 class WikiPageName
535 {
536     /** Short name for page.
537      *
538      * This is the value of $name passed to the constructor.
539      * (For use, e.g. as a default label for links to the page.)
540      */
541     //var $shortName;
542
543     /** The full page name.
544      *
545      * This is the full name of the page (without anchor).
546      */
547     //var $name;
548     
549     /** The anchor.
550      *
551      * This is the referenced anchor within the page, or the empty string.
552      */
553     //var $anchor;
554     
555     /** Constructor
556      *
557      * @param mixed $name Page name.
558      * WikiDB_Page, WikiDB_PageRevision, or string.
559      * This can be a relative subpage name (like '/SubPage'),
560      * or can be the empty string to refer to the $basename.
561      *
562      * @param string $anchor For links to anchors in page.
563      *
564      * @param mixed $basename Page name from which to interpret
565      * relative or other non-fully-specified page names.
566      */
567     function WikiPageName($name, $basename=false, $anchor=false) {
568         if (is_string($name)) {
569             $this->shortName = $name;
570         
571             if ($name == '' or $name[0] == SUBPAGE_SEPARATOR) {
572                 if ($basename)
573                     $name = $this->_pagename($basename) . $name;
574                 else
575                     $name = $this->_normalize_bad_pagename($name);
576             }
577         }
578         else {
579             $name = $this->_pagename($name);
580             $this->shortName = $name;
581         }
582
583         $this->name = $this->_check($name);
584         $this->anchor = (string)$anchor;
585     }
586
587     function getParent() {
588         $name = $this->name;
589         if (!($tail = strrchr($name, SUBPAGE_SEPARATOR)))
590             return false;
591         return substr($name, 0, -strlen($tail));
592     }
593
594     function isValid($strict = false) {
595         if ($strict)
596             return !isset($this->_errors);
597         return (is_string($this->name) and $this->name != '');
598     }
599
600     function getWarnings() {
601         $warnings = array();
602         if (isset($this->_warnings))
603             $warnings = array_merge($warnings, $this->_warnings);
604         if (isset($this->_errors))
605             $warnings = array_merge($warnings, $this->_errors);
606         if (!$warnings)
607             return false;
608         
609         return sprintf(_("'%s': Bad page name: %s"),
610                        $this->shortName, join(', ', $warnings));
611     }
612     
613     function _pagename($page) {
614         if (isa($page, 'WikiDB_Page'))
615             return $page->getName();
616         elseif (isa($page, 'WikiDB_PageRevision'))
617             return $page->getPageName();
618         elseif (isa($page, 'WikiPageName'))
619             return $page->name;
620         if (!is_string($page)) {
621             trigger_error(sprintf("Non-string pagename '%s' (%s)(%s)",
622                                   $page, gettype($page), get_class($page)),
623                           E_USER_NOTICE);
624         }
625         //assert(is_string($page));
626         return $page;
627     }
628
629     function _normalize_bad_pagename($name) {
630         trigger_error("Bad pagename: " . $name, E_USER_WARNING);
631
632         // Punt...  You really shouldn't get here.
633         if (empty($name)) {
634             global $request;
635             return $request->getArg('pagename');
636         }
637         assert($name[0] == SUBPAGE_SEPARATOR);
638         return substr($name, 1);
639     }
640
641
642     function _check($pagename) {
643         // Compress internal white-space to single space character.
644         $pagename = preg_replace('/[\s\xa0]+/', ' ', $orig = $pagename);
645         if ($pagename != $orig)
646             $this->_warnings[] = _("White space converted to single space");
647     
648         // Delete any control characters.
649         if (DATABASE_TYPE == 'cvs' or DATABASE_TYPE == 'file') {
650             $pagename = preg_replace('/[\x00-\x1f\x7f\x80-\x9f]/', '', $orig = $pagename);
651             if ($pagename != $orig)
652                 $this->_errors[] = _("Control characters not allowed");
653         }
654
655         // Strip leading and trailing white-space.
656         $pagename = trim($pagename);
657
658         $orig = $pagename;
659         while ($pagename and $pagename[0] == SUBPAGE_SEPARATOR)
660             $pagename = substr($pagename, 1);
661         if ($pagename != $orig)
662             $this->_errors[] = sprintf(_("Leading %s not allowed"), SUBPAGE_SEPARATOR);
663
664         // ";" is urlencoded, so safe from php arg-delim problems
665         /*if (strstr($pagename, ';')) {
666             $this->_warnings[] = _("';' is deprecated");
667             $pagename = str_replace(';', '', $pagename);
668         }*/
669         
670         // not only for the db backend, also to restrict url length
671         if (strlen($pagename) > MAX_PAGENAME_LENGTH) {
672             $pagename = substr($pagename, 0, MAX_PAGENAME_LENGTH);
673             $this->_errors[] = _("too long");
674         }
675
676         // disallow some chars only on file and cvs
677         if ((DATABASE_TYPE == 'cvs' or DATABASE_TYPE == 'file') 
678             and preg_match('/(:|\.\.)/', $pagename, $m)) {
679             $this->_warnings[] = sprintf(_("Illegal chars %s removed"), $m[1]);
680             $pagename = str_replace('..', '', $pagename);
681             $pagename = str_replace(':', '', $pagename);
682         }
683         
684         return $pagename;
685     }
686 }
687
688 /**
689  * Convert old page markup to new-style markup.
690  *
691  * @param string $text Old-style wiki markup.
692  *
693  * @param string $markup_type
694  * One of: <dl>
695  * <dt><code>"block"</code>  <dd>Convert all markup.
696  * <dt><code>"inline"</code> <dd>Convert only inline markup.
697  * <dt><code>"links"</code>  <dd>Convert only link markup.
698  * </dl>
699  *
700  * @return string New-style wiki markup.
701  *
702  * @bugs Footnotes don't work quite as before (esp if there are
703  *   multiple references to the same footnote.  But close enough,
704  *   probably for now....
705  * @bugs  Apache2 and IIS crash with OldTextFormattingRules or
706  *   AnciennesR%E8glesDeFormatage. (at the 2nd attempt to do the anchored block regex)
707  *   It only crashes with CreateToc so far, but other pages (not in pgsrc) are 
708  *   also known to crash, even with Apache1.
709  */
710 function ConvertOldMarkup ($text, $markup_type = "block") {
711
712     static $subs;
713     static $block_re;
714     
715     // FIXME:
716     // Trying to detect why the 2nd paragraph of OldTextFormattingRules or
717     // AnciennesR%E8glesDeFormatage crashes. 
718     // It only crashes with CreateToc so far, but other pages (not in pgsrc) are 
719     // also known to crash, even with Apache1.
720     $debug_skip = false;
721     // I suspect this only to crash with Apache2 and IIS.
722     if (in_array(php_sapi_name(),array('apache2handler','apache2filter','isapi'))
723         and preg_match("/plugin CreateToc/", $text)) 
724     {
725         trigger_error(_("The CreateTocPlugin is not yet old markup compatible! ")
726                      ._("Please remove the CreateToc line to be able to reformat this page to old markup. ")
727                      ._("Skipped."), E_USER_WARNING);
728         $debug_skip = true;
729         //if (!DEBUG) return $text;
730         return $text;
731     }
732
733     if (empty($subs)) {
734         /*****************************************************************
735          * Conversions for inline markup:
736          */
737
738         // escape tilde's
739         $orig[] = '/~/';
740         $repl[] = '~~';
741
742         // escape escaped brackets
743         $orig[] = '/\[\[/';
744         $repl[] = '~[';
745
746         // change ! escapes to ~'s.
747         global $WikiNameRegexp, $request;
748         $bang_esc[] = "(?:" . ALLOWED_PROTOCOLS . "):[^\s<>\[\]\"'()]*[^\s<>\[\]\"'(),.?]";
749         // before 4.3.9 pcre had a memory release bug, which might hit us here. so be safe.
750         if (check_php_version(4,3,9)) {
751           $map = getInterwikiMap();
752           if ($map_regex = $map->getRegexp())
753             $bang_esc[] = $map_regex . ":[^\\s.,;?()]+"; // FIXME: is this really needed?
754         }
755         $bang_esc[] = $WikiNameRegexp;
756         $orig[] = '/!((?:' . join(')|(', $bang_esc) . '))/';
757         $repl[] = '~\\1';
758
759         $subs["links"] = array($orig, $repl);
760
761         // Temporarily URL-encode pairs of underscores in links to hide
762         // them from the re for bold markup.
763         $orig[] = '/\[[^\[\]]*?__[^\[\]]*?\]/e';
764         $repl[] = 'str_replace(\'__\', \'%5F%5F\', \'\\0\')';
765
766         // Escape '<'s
767         //$orig[] = '/<(?!\?plugin)|(?<!^)</m';
768         //$repl[] = '~<';
769         
770         // Convert footnote references.
771         $orig[] = '/(?<=.)(?<!~)\[\s*(\d+)\s*\]/m';
772         $repl[] = '#[|ftnt_ref_\\1]<sup>~[[\\1|#ftnt_\\1]~]</sup>';
773
774         // Convert old style emphases to HTML style emphasis.
775         $orig[] = '/__(.*?)__/';
776         $repl[] = '<strong>\\1</strong>';
777         $orig[] = "/''(.*?)''/";
778         $repl[] = '<em>\\1</em>';
779
780         // Escape nestled markup.
781         $orig[] = '/^(?<=^|\s)[=_](?=\S)|(?<=\S)[=_*](?=\s|$)/m';
782         $repl[] = '~\\0';
783         
784         // in old markup headings only allowed at beginning of line
785         $orig[] = '/!/';
786         $repl[] = '~!';
787
788         // Convert URL-encoded pairs of underscores in links back to
789         // real underscores after bold markup has been converted.
790         $orig = '/\[[^\[\]]*?%5F%5F[^\[\]]*?\]/e';
791         $repl = 'str_replace(\'%5F%5F\', \'__\', \'\\0\')';
792
793         $subs["inline"] = array($orig, $repl);
794
795         /*****************************************************************
796          * Patterns which match block markup constructs which take
797          * special handling...
798          */
799
800         // Indented blocks
801         $blockpats[] = '[ \t]+\S(?:.*\s*\n[ \t]+\S)*';
802         // Tables
803         $blockpats[] = '\|(?:.*\n\|)*';
804
805         // List items
806         $blockpats[] = '[#*;]*(?:[*#]|;.*?:)';
807
808         // Footnote definitions
809         $blockpats[] = '\[\s*(\d+)\s*\]';
810
811         if (!$debug_skip) {
812         // Plugins
813         $blockpats[] = '<\?plugin(?:-form)?\b.*\?>\s*$';
814         }
815
816         // Section Title
817         $blockpats[] = '!{1,3}[^!]';
818         /*
819         removed .|\n in the anchor not to crash on /m because with /m "." already includes \n
820         this breaks headings but it doesn't crash anymore (crash on non-cgi, non-cli only)
821         */
822         $block_re = ( '/\A((?:.|\n)*?)(^(?:'
823                       . join("|", $blockpats)
824                       . ').*$)\n?/m' );
825         
826     }
827     
828     if ($markup_type != "block") {
829         list ($orig, $repl) = $subs[$markup_type];
830         return preg_replace($orig, $repl, $text);
831     }
832     else {
833         list ($orig, $repl) = $subs['inline'];
834         $out = '';
835         //FIXME:
836         // php crashes here in the 2nd paragraph of OldTextFormattingRules, 
837         // AnciennesR%E8glesDeFormatage and more 
838         // See http://www.pcre.org/pcre.txt LIMITATIONS
839          while (preg_match($block_re, $text, $m)) {
840             $text = substr($text, strlen($m[0]));
841             list (,$leading_text, $block) = $m;
842             $suffix = "\n";
843             
844             if (strchr(" \t", $block[0])) {
845                 // Indented block
846                 $prefix = "<pre>\n";
847                 $suffix = "\n</pre>\n";
848             }
849             elseif ($block[0] == '|') {
850                 // Old-style table
851                 $prefix = "<?plugin OldStyleTable\n";
852                 $suffix = "\n?>\n";
853             }
854             elseif (strchr("#*;", $block[0])) {
855                 // Old-style list item
856                 preg_match('/^([#*;]*)([*#]|;.*?:) */', $block, $m);
857                 list (,$ind,$bullet) = $m;
858                 $block = substr($block, strlen($m[0]));
859                 
860                 $indent = str_repeat('     ', strlen($ind));
861                 if ($bullet[0] == ';') {
862                     //$term = ltrim(substr($bullet, 1));
863                     //return $indent . $term . "\n" . $indent . '     ';
864                     $prefix = $ind . $bullet;
865                 }
866                 else
867                     $prefix = $indent . $bullet . ' ';
868             }
869             elseif ($block[0] == '[') {
870                 // Footnote definition
871                 preg_match('/^\[\s*(\d+)\s*\]/', $block, $m);
872                 $footnum = $m[1];
873                 $block = substr($block, strlen($m[0]));
874                 $prefix = "#[|ftnt_${footnum}]~[[${footnum}|#ftnt_ref_${footnum}]~] ";
875             }
876             elseif ($block[0] == '<') {
877                 // Plugin.
878                 // HACK: no inline markup...
879                 $prefix = $block;
880                 $block = '';
881             }
882             elseif ($block[0] == '!') {
883                 // Section heading
884                 preg_match('/^!{1,3}/', $block, $m);
885                 $prefix = $m[0];
886                 $block = substr($block, strlen($m[0]));
887             }
888             else {
889                 // AAck!
890                 assert(0);
891             }
892             if ($leading_text) $leading_text = preg_replace($orig, $repl, $leading_text);
893             if ($block) $block = preg_replace($orig, $repl, $block);
894             $out .= $leading_text;
895             $out .= $prefix;
896             $out .= $block;
897             $out .= $suffix;
898         }
899         return $out . preg_replace($orig, $repl, $text);
900     }
901 }
902
903
904 /**
905  * Expand tabs in string.
906  *
907  * Converts all tabs to (the appropriate number of) spaces.
908  *
909  * @param string $str
910  * @param integer $tab_width
911  * @return string
912  */
913 function expand_tabs($str, $tab_width = 8) {
914     $split = split("\t", $str);
915     $tail = array_pop($split);
916     $expanded = "\n";
917     foreach ($split as $hunk) {
918         $expanded .= $hunk;
919         $pos = strlen(strrchr($expanded, "\n")) - 1;
920         $expanded .= str_repeat(" ", ($tab_width - $pos % $tab_width));
921     }
922     return substr($expanded, 1) . $tail;
923 }
924
925 /**
926  * Split WikiWords in page names.
927  *
928  * It has been deemed useful to split WikiWords (into "Wiki Words") in
929  * places like page titles. This is rumored to help search engines
930  * quite a bit.
931  *
932  * @param $page string The page name.
933  *
934  * @return string The split name.
935  */
936 function SplitPagename ($page) {
937     
938     if (preg_match("/\s/", $page))
939         return $page;           // Already split --- don't split any more.
940     
941     // This algorithm is specialized for several languages.
942     // (Thanks to Pierrick MEIGNEN)
943     // Improvements for other languages welcome.
944     static $RE;
945     if (!isset($RE)) {
946         // This mess splits between a lower-case letter followed by
947         // either an upper-case or a numeral; except that it wont
948         // split the prefixes 'Mc', 'De', or 'Di' off of their tails.
949         switch ($GLOBALS['LANG']) {
950         case 'en':
951         case 'it':
952         case 'es': 
953         case 'de':
954             $RE[] = '/([[:lower:]])((?<!Mc|De|Di)[[:upper:]]|\d)/';
955             break;
956         case 'fr': 
957             $RE[] = '/([[:lower:]])((?<!Mc|Di)[[:upper:]]|\d)/';
958             break;
959         }
960         $sep = preg_quote(SUBPAGE_SEPARATOR, '/');
961         // This the single-letter words 'I' and 'A' from any following
962         // capitalized words.
963         switch ($GLOBALS['LANG']) {
964         case 'en': 
965             $RE[] = "/(?<= |${sep}|^)([AI])([[:upper:]][[:lower:]])/";
966             break;
967         case 'fr': 
968             $RE[] = "/(?<= |${sep}|^)([À])([[:upper:]][[:lower:]])/";
969             break;
970         }
971         // Split numerals from following letters.
972         $RE[] = '/(\d)([[:alpha:]])/';
973         // Split at subpage seperators. TBD in Theme.php
974         $RE[] = "/([^${sep}]+)(${sep})/";
975         
976         foreach ($RE as $key)
977             $RE[$key] = pcre_fix_posix_classes($key);
978     }
979
980     foreach ($RE as $regexp) {
981         $page = preg_replace($regexp, '\\1 \\2', $page);
982     }
983     return $page;
984 }
985
986 function NoSuchRevision (&$request, $page, $version) {
987     $html = HTML(HTML::h2(_("Revision Not Found")),
988                  HTML::p(fmt("I'm sorry.  Version %d of %s is not in the database.",
989                              $version, WikiLink($page, 'auto'))));
990     include_once('lib/Template.php');
991     GeneratePage($html, _("Bad Version"), $page->getCurrentRevision());
992     $request->finish();
993 }
994
995
996 /**
997  * Get time offset for local time zone.
998  *
999  * @param $time time_t Get offset for this time. Default: now.
1000  * @param $no_colon boolean Don't put colon between hours and minutes.
1001  * @return string Offset as a string in the format +HH:MM.
1002  */
1003 function TimezoneOffset ($time = false, $no_colon = false) {
1004     if ($time === false)
1005         $time = time();
1006     $secs = date('Z', $time);
1007
1008     if ($secs < 0) {
1009         $sign = '-';
1010         $secs = -$secs;
1011     }
1012     else {
1013         $sign = '+';
1014     }
1015     $colon = $no_colon ? '' : ':';
1016     $mins = intval(($secs + 30) / 60);
1017     return sprintf("%s%02d%s%02d",
1018                    $sign, $mins / 60, $colon, $mins % 60);
1019 }
1020
1021
1022 /**
1023  * Format time in ISO-8601 format.
1024  *
1025  * @param $time time_t Time.  Default: now.
1026  * @return string Date and time in ISO-8601 format.
1027  */
1028 function Iso8601DateTime ($time = false) {
1029     if ($time === false)
1030         $time = time();
1031     $tzoff = TimezoneOffset($time);
1032     $date  = date('Y-m-d', $time);
1033     $time  = date('H:i:s', $time);
1034     return $date . 'T' . $time . $tzoff;
1035 }
1036
1037 /**
1038  * Format time in RFC-2822 format.
1039  *
1040  * @param $time time_t Time.  Default: now.
1041  * @return string Date and time in RFC-2822 format.
1042  */
1043 function Rfc2822DateTime ($time = false) {
1044     if ($time === false)
1045         $time = time();
1046     return date('D, j M Y H:i:s ', $time) . TimezoneOffset($time, 'no colon');
1047 }
1048
1049 /**
1050  * Format time in RFC-1123 format.
1051  *
1052  * @param $time time_t Time.  Default: now.
1053  * @return string Date and time in RFC-1123 format.
1054  */
1055 function Rfc1123DateTime ($time = false) {
1056     if ($time === false)
1057         $time = time();
1058     return gmdate('D, d M Y H:i:s \G\M\T', $time);
1059 }
1060
1061 /** Parse date in RFC-1123 format.
1062  *
1063  * According to RFC 1123 we must accept dates in the following
1064  * formats:
1065  *
1066  *   Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
1067  *   Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
1068  *   Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
1069  *
1070  * (Though we're only allowed to generate dates in the first format.)
1071  */
1072 function ParseRfc1123DateTime ($timestr) {
1073     $timestr = trim($timestr);
1074     if (preg_match('/^ \w{3},\s* (\d{1,2}) \s* (\w{3}) \s* (\d{4}) \s*'
1075                    .'(\d\d):(\d\d):(\d\d) \s* GMT $/ix',
1076                    $timestr, $m)) {
1077         list(, $mday, $mon, $year, $hh, $mm, $ss) = $m;
1078     }
1079     elseif (preg_match('/^ \w+,\s* (\d{1,2})-(\w{3})-(\d{2}|\d{4}) \s*'
1080                        .'(\d\d):(\d\d):(\d\d) \s* GMT $/ix',
1081                        $timestr, $m)) {
1082         list(, $mday, $mon, $year, $hh, $mm, $ss) = $m;
1083         if ($year < 70) $year += 2000;
1084         elseif ($year < 100) $year += 1900;
1085     }
1086     elseif (preg_match('/^\w+\s* (\w{3}) \s* (\d{1,2}) \s*'
1087                        .'(\d\d):(\d\d):(\d\d) \s* (\d{4})$/ix',
1088                        $timestr, $m)) {
1089         list(, $mon, $mday, $hh, $mm, $ss, $year) = $m;
1090     }
1091     else {
1092         // Parse failed.
1093         return false;
1094     }
1095
1096     $time = strtotime("$mday $mon $year ${hh}:${mm}:${ss} GMT");
1097     if ($time == -1)
1098         return false;           // failed
1099     return $time;
1100 }
1101
1102 /**
1103  * Format time to standard 'ctime' format.
1104  *
1105  * @param $time time_t Time.  Default: now.
1106  * @return string Date and time.
1107  */
1108 function CTime ($time = false)
1109 {
1110     if ($time === false)
1111         $time = time();
1112     return date("D M j H:i:s Y", $time);
1113 }
1114
1115
1116 /**
1117  * Format number as kilobytes or bytes.
1118  * Short format is used for PageList
1119  * Long format is used in PageInfo
1120  *
1121  * @param $bytes       int.  Default: 0.
1122  * @param $longformat  bool. Default: false.
1123  * @return class FormattedText (XmlElement.php).
1124  */
1125 function ByteFormatter ($bytes = 0, $longformat = false) {
1126     if ($bytes < 0)
1127         return fmt("-???");
1128     if ($bytes < 1024) {
1129         if (! $longformat)
1130             $size = fmt("%s b", $bytes);
1131         else
1132             $size = fmt("%s bytes", $bytes);
1133     }
1134     else {
1135         $kb = round($bytes / 1024, 1);
1136         if (! $longformat)
1137             $size = fmt("%s k", $kb);
1138         else
1139             $size = fmt("%s Kb (%s bytes)", $kb, $bytes);
1140     }
1141     return $size;
1142 }
1143
1144 /**
1145  * Internationalized printf.
1146  *
1147  * This is essentially the same as PHP's built-in printf
1148  * with the following exceptions:
1149  * <ol>
1150  * <li> It passes the format string through gettext().
1151  * <li> It supports the argument reordering extensions.
1152  * </ol>
1153  *
1154  * Example:
1155  *
1156  * In php code, use:
1157  * <pre>
1158  *    __printf("Differences between versions %s and %s of %s",
1159  *             $new_link, $old_link, $page_link);
1160  * </pre>
1161  *
1162  * Then in locale/po/de.po, one can reorder the printf arguments:
1163  *
1164  * <pre>
1165  *    msgid "Differences between %s and %s of %s."
1166  *    msgstr "Der Unterschiedsergebnis von %3$s, zwischen %1$s und %2$s."
1167  * </pre>
1168  *
1169  * (Note that while PHP tries to expand $vars within double-quotes,
1170  * the values in msgstr undergo no such expansion, so the '$'s
1171  * okay...)
1172  *
1173  * One shouldn't use reordered arguments in the default format string.
1174  * Backslashes in the default string would be necessary to escape the
1175  * '$'s, and they'll cause all kinds of trouble....
1176  */ 
1177 function __printf ($fmt) {
1178     $args = func_get_args();
1179     array_shift($args);
1180     echo __vsprintf($fmt, $args);
1181 }
1182
1183 /**
1184  * Internationalized sprintf.
1185  *
1186  * This is essentially the same as PHP's built-in printf with the
1187  * following exceptions:
1188  *
1189  * <ol>
1190  * <li> It passes the format string through gettext().
1191  * <li> It supports the argument reordering extensions.
1192  * </ol>
1193  *
1194  * @see __printf
1195  */ 
1196 function __sprintf ($fmt) {
1197     $args = func_get_args();
1198     array_shift($args);
1199     return __vsprintf($fmt, $args);
1200 }
1201
1202 /**
1203  * Internationalized vsprintf.
1204  *
1205  * This is essentially the same as PHP's built-in printf with the
1206  * following exceptions:
1207  *
1208  * <ol>
1209  * <li> It passes the format string through gettext().
1210  * <li> It supports the argument reordering extensions.
1211  * </ol>
1212  *
1213  * @see __printf
1214  */ 
1215 function __vsprintf ($fmt, $args) {
1216     $fmt = gettext($fmt);
1217     // PHP's sprintf doesn't support variable with specifiers,
1218     // like sprintf("%*s", 10, "x"); --- so we won't either.
1219     
1220     if (preg_match_all('/(?<!%)%(\d+)\$/x', $fmt, $m)) {
1221         // Format string has '%2$s' style argument reordering.
1222         // PHP doesn't support this.
1223         if (preg_match('/(?<!%)%[- ]?\d*[^- \d$]/x', $fmt))
1224             // literal variable name substitution only to keep locale
1225             // strings uncluttered
1226             trigger_error(sprintf(_("Can't mix '%s' with '%s' type format strings"),
1227                                   '%1\$s','%s'), E_USER_WARNING); //php+locale error
1228         
1229         $fmt = preg_replace('/(?<!%)%\d+\$/x', '%', $fmt);
1230         $newargs = array();
1231         
1232         // Reorder arguments appropriately.
1233         foreach($m[1] as $argnum) {
1234             if ($argnum < 1 || $argnum > count($args))
1235                 trigger_error(sprintf(_("%s: argument index out of range"), 
1236                                       $argnum), E_USER_WARNING);
1237             $newargs[] = $args[$argnum - 1];
1238         }
1239         $args = $newargs;
1240     }
1241     
1242     // Not all PHP's have vsprintf, so...
1243     array_unshift($args, $fmt);
1244     return call_user_func_array('sprintf', $args);
1245 }
1246
1247 function file_mtime ($filename) {
1248     if ($stat = @stat($filename))
1249         return $stat[9];
1250     else 
1251         return false;
1252 }
1253
1254 function sort_file_mtime ($a, $b) {
1255     $ma = file_mtime($a);
1256     $mb = file_mtime($b);
1257     if (!$ma or !$mb or $ma == $mb) return 0;
1258     return ($ma > $mb) ? -1 : 1;
1259 }
1260
1261 class fileSet {
1262     /**
1263      * Build an array in $this->_fileList of files from $dirname.
1264      * Subdirectories are not traversed.
1265      *
1266      * (This was a function LoadDir in lib/loadsave.php)
1267      * See also http://www.php.net/manual/en/function.readdir.php
1268      */
1269     function getFiles($exclude=false, $sortby=false, $limit=false) {
1270         $list = $this->_fileList;
1271
1272         if ($sortby) {
1273             require_once('lib/PageList.php');
1274             switch (Pagelist::sortby($sortby, 'db')) {
1275             case 'pagename ASC': break;
1276             case 'pagename DESC': 
1277                 $list = array_reverse($list); 
1278                 break;
1279             case 'mtime ASC': 
1280                 usort($list,'sort_file_mtime'); 
1281                 break;
1282             case 'mtime DESC': 
1283                 usort($list,'sort_file_mtime');
1284                 $list = array_reverse($list); 
1285                 break;
1286             }
1287         }
1288         if ($limit)
1289             return array_splice($list, 0, $limit);
1290         return $list;
1291     }
1292
1293     function _filenameSelector($filename) {
1294         if (! $this->_pattern)
1295             return true;
1296         else {
1297             return glob_match ($this->_pattern, $filename, $this->_case);
1298         }
1299     }
1300
1301     function fileSet($directory, $filepattern = false) {
1302         $this->_fileList = array();
1303         $this->_pattern = $filepattern;
1304         $this->_case = !isWindows();
1305         $this->_pathsep = '/';
1306
1307         if (empty($directory)) {
1308             trigger_error(sprintf(_("%s is empty."), 'directoryname'),
1309                           E_USER_NOTICE);
1310             return; // early return
1311         }
1312
1313         @ $dir_handle = opendir($dir=$directory);
1314         if (empty($dir_handle)) {
1315             trigger_error(sprintf(_("Unable to open directory '%s' for reading"),
1316                                   $dir), E_USER_NOTICE);
1317             return; // early return
1318         }
1319
1320         while ($filename = readdir($dir_handle)) {
1321             if ($filename[0] == '.' || filetype($dir . $this->_pathsep . $filename) != 'file')
1322                 continue;
1323             if ($this->_filenameSelector($filename)) {
1324                 array_push($this->_fileList, "$filename");
1325                 //trigger_error(sprintf(_("found file %s"), $filename),
1326                 //                      E_USER_NOTICE); //debugging
1327             }
1328         }
1329         closedir($dir_handle);
1330     }
1331 };
1332
1333 // File globbing
1334
1335 // expands a list containing regex's to its matching entries
1336 class ListRegexExpand {
1337     //var $match, $list, $index, $case_sensitive;
1338     function ListRegexExpand (&$list, $match, $case_sensitive = true) {
1339         $this->match = str_replace('/','\/',$match);
1340         $this->list = &$list;
1341         $this->case_sensitive = $case_sensitive;        
1342         //$this->index = false;
1343     }
1344     function listMatchCallback ($item, $key) {
1345         if (preg_match('/' . $this->match . ($this->case_sensitive ? '/' : '/i'), $item)) {
1346             unset($this->list[$this->index]);
1347             $this->list[] = $item;
1348         }
1349     }
1350     function expandRegex ($index, &$pages) {
1351         $this->index = $index;
1352         array_walk($pages, array($this, 'listMatchCallback'));
1353         return $this->list;
1354     }
1355 }
1356
1357 // convert fileglob to regex style:
1358 // convert some wildcards to pcre style, escape the rest
1359 // escape . \\ + * ? [ ^ ] $ ( ) { } = ! < > | : 
1360 function glob_to_pcre ($glob) {
1361     // check simple case: no need to escape
1362     if (strcspn($glob, ".\\+*?[^]$(){}=!<>|:") == strlen($glob))
1363         return $glob;
1364     // preg_replace cannot handle "\\\\\\2" so convert \\ to \xff
1365     $glob = strtr($glob, "\\", "\xff");
1366     // first convert some unescaped expressions to pcre style: . => \.
1367     $escape = ".^$";
1368     $re = preg_replace('/([^\xff])?(['.preg_quote($escape).'])/', "\\1\xff\\2", $glob);
1369
1370     // * => .*, ? => .
1371     $re = preg_replace('/([^\xff])?\*/', '$1.*', $re);
1372     $re = preg_replace('/([^\xff])?\?/', '$1.', $re);
1373     if (!preg_match('/^[\?\*]/',$glob))
1374         $re = '^' . $re;
1375     if (!preg_match('/[\?\*]$/',$glob))
1376         $re = $re . '$';
1377
1378     // .*? handled above, now escape the rest
1379     $escape = '\[](){}=!<>|:';
1380     while (strcspn($re, $escape) != strlen($re)) // loop strangely needed
1381         $re = preg_replace('/([^\xff])(['.preg_quote($escape).'])/', "\\1\xff\\2", $re);
1382     return strtr($re, "\xff", "\\");
1383 }
1384
1385 function glob_match ($glob, $against, $case_sensitive = true) {
1386     return preg_match('/' . glob_to_pcre($glob) . ($case_sensitive ? '/' : '/i'), $against);
1387 }
1388
1389 function explodeList($input, $allnames, $glob_style = true, $case_sensitive = true) {
1390     $list = explode(',',$input);
1391     // expand wildcards from list of $allnames
1392     if (preg_match('/[\?\*]/',$input)) {
1393         // Optimizing loop invariants:
1394         // http://phplens.com/lens/php-book/optimizing-debugging-php.php
1395         for ($i = 0, $max = sizeof($list); $i < $max; $i++) {
1396             $f = $list[$i];
1397             if (preg_match('/[\?\*]/',$f)) {
1398                 reset($allnames);
1399                 $expand = new ListRegexExpand($list, $glob_style ? glob_to_pcre($f) : $f, $case_sensitive);
1400                 $expand->expandRegex($i, $allnames);
1401             }
1402         }
1403     }
1404     return $list;
1405 }
1406
1407 // echo implode(":",explodeList("Test*",array("xx","Test1","Test2")));
1408 function explodePageList($input, $include_empty=false, $sortby='pagename', $limit=false, $exclude=false) {
1409     include_once("lib/PageList.php");
1410     return PageList::explodePageList($input, $include_empty, $sortby, $limit, $exclude);
1411 }
1412
1413 // Class introspections
1414
1415 /** 
1416  * Determine whether object is of a specified type.
1417  * In PHP builtin since 4.2.0 as is_a()
1418  *
1419  * @param $object object An object.
1420  * @param $class string Class name.
1421  * @return bool True iff $object is a $class
1422  * or a sub-type of $class. 
1423  */
1424 function isa ($object, $class) {
1425     //if (check_php_version(5)) 
1426     //    return $object instanceof $class;
1427     if (check_php_version(4,2) and !check_php_version(5)) 
1428         return is_a($object, $class);
1429
1430     $lclass = check_php_version(5) ? $class : strtolower($class);
1431     return is_object($object)
1432         && ( strtolower(get_class($object)) == strtolower($class)
1433              || is_subclass_of($object, $lclass) );
1434 }
1435
1436 /** Determine whether (possible) object has method.
1437  *
1438  * @param $object mixed Object
1439  * @param $method string Method name
1440  * @return bool True iff $object is an object with has method $method.
1441  */
1442 function can ($object, $method) {
1443     return is_object($object) && method_exists($object, strtolower($method));
1444 }
1445
1446 /** Determine whether a function is okay to use.
1447  *
1448  * Some providers (e.g. Lycos) disable some of PHP functions for
1449  * "security reasons."  This makes those functions, of course,
1450  * unusable, despite the fact the function_exists() says they
1451  * exist.
1452  *
1453  * This function test to see if a function exists and is not
1454  * disallowed by PHP's disable_functions config setting.
1455  *
1456  * @param string $function_name  Function name
1457  * @return bool  True iff function can be used.
1458  */
1459 function function_usable($function_name) {
1460     static $disabled;
1461     if (!is_array($disabled)) {
1462         $disabled = array();
1463         // Use get_cfg_var since ini_get() is one of the disabled functions
1464         // (on Lycos, at least.)
1465         $split = preg_split('/\s*,\s*/', trim(get_cfg_var('disable_functions')));
1466         foreach ($split as $f)
1467             $disabled[strtolower($f)] = true;
1468     }
1469
1470     return ( function_exists($function_name)
1471              and ! isset($disabled[strtolower($function_name)])
1472              );
1473 }
1474     
1475     
1476 /** Hash a value.
1477  *
1478  * This is used for generating ETags.
1479  */
1480 function hash ($x) {
1481     if (is_scalar($x)) {
1482         return $x;
1483     }
1484     elseif (is_array($x)) {            
1485         ksort($x);
1486         return md5(serialize($x));
1487     }
1488     elseif (is_object($x)) {
1489         return $x->hash();
1490     }
1491     trigger_error("Can't hash $x", E_USER_ERROR);
1492 }
1493
1494     
1495 /**
1496  * Seed the random number generator.
1497  *
1498  * better_srand() ensures the randomizer is seeded only once.
1499  * 
1500  * How random do you want it? See:
1501  * http://www.php.net/manual/en/function.srand.php
1502  * http://www.php.net/manual/en/function.mt-srand.php
1503  */
1504 function better_srand($seed = '') {
1505     static $wascalled = FALSE;
1506     if (!$wascalled) {
1507         $seed = $seed === '' ? (double) microtime() * 1000000 : $seed;
1508         function_exists('mt_srand') ? mt_srand($seed) : srand($seed);
1509         $wascalled = TRUE;
1510         //trigger_error("new random seed", E_USER_NOTICE); //debugging
1511     }
1512 }
1513
1514 /**
1515  * Recursively count all non-empty elements 
1516  * in array of any dimension or mixed - i.e. 
1517  * array('1' => 2, '2' => array('1' => 3, '2' => 4))
1518  * See http://www.php.net/manual/en/function.count.php
1519  */
1520 function count_all($arg) {
1521     // skip if argument is empty
1522     if ($arg) {
1523         //print_r($arg); //debugging
1524         $count = 0;
1525         // not an array, return 1 (base case) 
1526         if(!is_array($arg))
1527             return 1;
1528         // else call recursively for all elements $arg
1529         foreach($arg as $key => $val)
1530             $count += count_all($val);
1531         return $count;
1532     }
1533 }
1534
1535 function isSubPage($pagename) {
1536     return (strstr($pagename, SUBPAGE_SEPARATOR));
1537 }
1538
1539 function subPageSlice($pagename, $pos) {
1540     $pages = explode(SUBPAGE_SEPARATOR,$pagename);
1541     $pages = array_slice($pages,$pos,1);
1542     return $pages[0];
1543 }
1544
1545 /**
1546  * Alert
1547  *
1548  * Class for "popping up" and alert box.  (Except that right now, it doesn't
1549  * pop up...)
1550  *
1551  * FIXME:
1552  * This is a hackish and needs to be refactored.  However it would be nice to
1553  * unify all the different methods we use for showing Alerts and Dialogs.
1554  * (E.g. "Page deleted", login form, ...)
1555  */
1556 class Alert {
1557     /** Constructor
1558      *
1559      * @param object $request
1560      * @param mixed $head  Header ("title") for alert box.
1561      * @param mixed $body  The text in the alert box.
1562      * @param hash $buttons  An array mapping button labels to URLs.
1563      *    The default is a single "Okay" button pointing to $request->getURLtoSelf().
1564      */
1565     function Alert($head, $body, $buttons=false) {
1566         if ($buttons === false)
1567             $buttons = array();
1568
1569         $this->_tokens = array('HEADER' => $head, 'CONTENT' => $body);
1570         $this->_buttons = $buttons;
1571     }
1572
1573     /**
1574      * Show the alert box.
1575      */
1576     function show() {
1577         global $request;
1578
1579         $tokens = $this->_tokens;
1580         $tokens['BUTTONS'] = $this->_getButtons();
1581         
1582         $request->discardOutput();
1583         $tmpl = new Template('dialog', $request, $tokens);
1584         $tmpl->printXML();
1585         $request->finish();
1586     }
1587
1588
1589     function _getButtons() {
1590         global $request;
1591
1592         $buttons = $this->_buttons;
1593         if (!$buttons)
1594             $buttons = array(_("Okay") => $request->getURLtoSelf());
1595         
1596         global $WikiTheme;
1597         foreach ($buttons as $label => $url)
1598             print "$label $url\n";
1599             $out[] = $WikiTheme->makeButton($label, $url, 'wikiaction');
1600         return new XmlContent($out);
1601     }
1602 }
1603
1604 // 1.3.8     => 1030.08
1605 // 1.3.9-p1  => 1030.091
1606 // 1.3.10pre => 1030.099
1607 // 1.3.11pre-20041120 => 1030.1120041120
1608 function phpwiki_version() {
1609     static $PHPWIKI_VERSION;
1610     if (!isset($PHPWIKI_VERSION)) {
1611         $arr = explode('.',preg_replace('/\D+$/','', PHPWIKI_VERSION)); // remove the pre
1612         $arr[2] = preg_replace('/\.+/','.',preg_replace('/\D/','.',$arr[2]));
1613         $PHPWIKI_VERSION = $arr[0]*1000 + $arr[1]*10 + 0.01*$arr[2];
1614         if (strstr(PHPWIKI_VERSION, 'pre'))
1615             $PHPWIKI_VERSION -= 0.01;
1616     }
1617     return $PHPWIKI_VERSION;
1618 }
1619
1620 function isWikiWord($word) {
1621     global $WikiNameRegexp;
1622     //or preg_match('/\A' . $WikiNameRegexp . '\z/', $word) ??
1623     return preg_match("/^$WikiNameRegexp\$/",$word);
1624 }
1625
1626 // needed to store serialized objects-values only (perm, pref)
1627 function obj2hash ($obj, $exclude = false, $fields = false) {
1628     $a = array();
1629     if (! $fields ) $fields = get_object_vars($obj);
1630     foreach ($fields as $key => $val) {
1631         if (is_array($exclude)) {
1632             if (in_array($key,$exclude)) continue;
1633         }
1634         $a[$key] = $val;
1635     }
1636     return $a;
1637 }
1638
1639 /**
1640  * isUtf8String($string) - cheap utf-8 detection
1641  *
1642  * segfaults for strings longer than 10kb!
1643  * Use http://www.phpdiscuss.com/article.php?id=565&group=php.i18n or
1644  * checkTitleEncoding() at http://cvs.sourceforge.net/viewcvs.py/wikipedia/phase3/languages/Language.php
1645  */
1646 function isUtf8String( $s ) {
1647     $ptrASCII  = '[\x00-\x7F]';
1648     $ptr2Octet = '[\xC2-\xDF][\x80-\xBF]';
1649     $ptr3Octet = '[\xE0-\xEF][\x80-\xBF]{2}';
1650     $ptr4Octet = '[\xF0-\xF4][\x80-\xBF]{3}';
1651     $ptr5Octet = '[\xF8-\xFB][\x80-\xBF]{4}';
1652     $ptr6Octet = '[\xFC-\xFD][\x80-\xBF]{5}';
1653     return preg_match("/^($ptrASCII|$ptr2Octet|$ptr3Octet|$ptr4Octet|$ptr5Octet|$ptr6Octet)*$/s", $s);
1654 }
1655
1656 /** 
1657  * Check for UTF-8 URLs; Internet Explorer produces these if you
1658  * type non-ASCII chars in the URL bar or follow unescaped links.
1659  * Requires urldecoded pagename.
1660  * Fixes sf.net bug #953949
1661  *
1662  * src: languages/Language.php:checkTitleEncoding() from mediawiki
1663  */
1664 function fixTitleEncoding( $s ) {
1665     global $charset;
1666
1667     $s = trim($s);
1668     // print a warning?
1669     if (empty($s)) return $s;
1670
1671     $ishigh = preg_match( '/[\x80-\xff]/', $s);
1672     /*
1673     $isutf = ($ishigh ? preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
1674                                     '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s ) : true );
1675     */
1676     $isutf = ($ishigh ? isUtf8String($s) : true);
1677     $locharset = strtolower($charset);
1678
1679     if( $locharset != "utf-8" and $ishigh and $isutf )
1680         // if charset == 'iso-8859-1' then simply use utf8_decode()
1681         if ($locharset == 'iso-8859-1')
1682             return utf8_decode( $s );
1683         else
1684             // TODO: check for iconv support
1685             return iconv( "UTF-8", $charset, $s );
1686
1687     if ($locharset == "utf-8" and $ishigh and !$isutf )
1688         return utf8_encode( $s );
1689
1690     // Other languages can safely leave this function, or replace
1691     // it with one to detect and convert another legacy encoding.
1692     return $s;
1693 }
1694
1695 /** 
1696  * MySQL fulltext index doesn't grok utf-8, so we
1697  * need to fold cases and convert to hex.
1698  * src: languages/Language.php:stripForSearch() from mediawiki
1699  */
1700 /*
1701 function stripForSearch( $string ) {
1702     global $wikiLowerChars; 
1703     // '/(?:[a-z]|\xc3[\x9f-\xbf]|\xc4[\x81\x83\x85\x87])/' => "a-z\xdf-\xf6\xf8-\xff"
1704     return preg_replace(
1705                         "/([\\xc0-\\xff][\\x80-\\xbf]*)/e",
1706                         "'U8' . bin2hex( strtr( \"\$1\", \$wikiLowerChars ) )",
1707                         $string );
1708 }
1709 */
1710
1711 /** 
1712  * Workaround for allow_url_fopen, to get the content of an external URI.
1713  * It returns the contents in one slurp. Parsers might want to check for allow_url_fopen
1714  * and use fopen, fread chunkwise. (see lib/XmlParser.php)
1715  */
1716 function url_get_contents( $uri ) {
1717     if (get_cfg_var('allow_url_fopen')) { // was ini_get('allow_url_fopen'))
1718         return @file_get_contents($uri);
1719     } else {
1720         require_once("lib/HttpClient.php");
1721         $bits = parse_url($uri);
1722         $host = $bits['host'];
1723         $port = isset($bits['port']) ? $bits['port'] : 80;
1724         $path = isset($bits['path']) ? $bits['path'] : '/';
1725         if (isset($bits['query'])) {
1726             $path .= '?'.$bits['query'];
1727         }
1728         $client = new HttpClient($host, $port);
1729         $client->use_gzip = false;
1730         if (!$client->get($path)) {
1731             return false;
1732         } else {
1733             return $client->getContent();
1734         }
1735     }
1736 }
1737
1738 /**
1739  * Generate consecutively named strings:
1740  *   Name, Name2, Name3, ...
1741  */
1742 function GenerateId($name) {
1743     static $ids = array();
1744     if (empty($ids[$name])) {
1745         $ids[$name] = 1;
1746         return $name;
1747     } else {
1748         $ids[$name]++;
1749         return $name . $ids[$name];
1750     }
1751 }
1752
1753 // from IncludePage. To be of general use.
1754 // content: string or array of strings
1755 function firstNWordsOfContent( $n, $content ) {
1756     if ($content and $n > 0) {
1757         if (is_array($content)) {
1758             // fixme: return a list of lines then?
1759             $content = join("\n", $content);
1760             $return_array = true;
1761             $wordcount = 0;
1762             foreach ($content as $line) {
1763                 $words = explode(' ', $line);
1764                 if ($wordcount + count($words) > $n) {
1765                     $new[] = implode(' ', array_slice($words, 0, $n - $wordcount))
1766                            . sprintf(_("... (first %s words)"), $n);
1767                     return $new;
1768                 } else {
1769                     $wordcount += count($words);
1770                     $new[] = $line;
1771                 }
1772             }
1773             return $new;
1774         } else {
1775             // fixme: use better whitespace/word seperators
1776             $words = explode(' ', $content);
1777             if (count($words) > $n) {
1778                 return join(' ', array_slice($words, 0, $n))
1779                        . sprintf(_("... (first %s words)"), $n);
1780             } else {
1781                 return $content;
1782             }
1783         }
1784     } else {
1785         return '';
1786     }
1787 }
1788
1789 // moved from lib/plugin/IncludePage.php
1790 function extractSection ($section, $content, $page, $quiet = false, $sectionhead = false) {
1791     $qsection = preg_replace('/\s+/', '\s+', preg_quote($section, '/'));
1792
1793     if (preg_match("/ ^(!{1,})\\s*$qsection" // section header
1794                    . "  \\s*$\\n?"           // possible blank lines
1795                    . "  ( (?: ^.*\\n? )*? )" // some lines
1796                    . "  (?= ^\\1 | \\Z)/xm", // sec header (same or higher level) (or EOF)
1797                    implode("\n", $content),
1798                    $match)) {
1799         // Strip trailing blanks lines and ---- <hr>s
1800         $text = preg_replace("/\\s*^-{4,}\\s*$/m", "", $match[2]);
1801         if ($sectionhead)
1802             $text = $match[1] . $section ."\n". $text;
1803         return explode("\n", $text);
1804     }
1805     if ($quiet)
1806         $mesg = $page ." ". $section;
1807     else
1808         $mesg = $section;
1809     return array(sprintf(_("<%s: no such section>"), $mesg));
1810 }
1811
1812 // use this faster version: only load ExternalReferrer if we came from an external referrer
1813 function isExternalReferrer(&$request) {
1814     if ($referrer = $request->get('HTTP_REFERER')) {
1815         $home = SERVER_URL; // SERVER_URL or SCRIPT_NAME, if we want to check sister wiki's also
1816         if (string_starts_with(strtolower($referrer), strtolower($home))) return false;
1817         require_once("lib/ExternalReferrer.php");
1818         $se = new SearchEngines();
1819         return $se->parseSearchQuery($referrer);
1820     }
1821     return false;
1822 }
1823
1824 /**
1825  * useful for PECL overrides: cvsclient, ldap, soap.
1826  */
1827 function loadPhpExtension($extension) {
1828     if (!extension_loaded($extension)) {
1829         $soname = (isWindows() ? 'php_' : '') . $extension . (isWindows() ? '.dll' : '.so');
1830         if (!@dl($soname))
1831             return false;
1832     }
1833     return extension_loaded($extension);
1834 }
1835
1836 function string_starts_with($string, $prefix) {
1837     return (substr($string, 0, strlen($prefix)) == $prefix);
1838 }
1839
1840 /** 
1841  * Ensure that the script will have another $secs time left. 
1842  * Works only if safe_mode is off.
1843  * For example not to timeout on waiting socket connections.
1844  *   Use the socket timeout as arg.
1845  */
1846 function longer_timeout($secs = 30) {
1847     $timeout = @ini_get("max_execution_time") ? ini_get("max_execution_time") : 30;
1848     $timeleft = $timeout - $GLOBALS['RUNTIMER']->getTime();
1849     if ($timeleft < $secs)
1850         @set_time_limit(max($timeout,(integer)($secs + $timeleft)));
1851 }
1852
1853 function printSimpleTrace($bt) {
1854     //print_r($bt);
1855     echo "Traceback:\n";
1856     foreach ($bt as $i => $elem) {
1857         if (!array_key_exists('file', $elem)) {
1858             continue;
1859         }
1860         echo join(" ",array_values($elem)),"\n";
1861         //print "  " . $elem['file'] . ':' . $elem['line'] . " " .$elem['function']"\n";
1862     }
1863 }
1864
1865 // $Log: not supported by cvs2svn $
1866 // Revision 1.227  2005/01/14 18:32:08  uckelman
1867 // ConvertOldMarkup did not properly handle links containing pairs of pairs
1868 // of underscores. (E.g., [http://example.com/foo__bar__.html] would be
1869 // munged by the regex for bold text.) Now '__' in links are hidden prior to
1870 // conversion of '__' into '<strong>', and then unhidden afterwards.
1871 //
1872 // Revision 1.226  2004/12/26 17:12:06  rurban
1873 // avoid stdargs in url, php5 fixes
1874 //
1875 // Revision 1.225  2004/12/22 19:02:29  rurban
1876 // fix glob for starting * or ?
1877 //
1878 // Revision 1.224  2004/12/20 12:11:50  rurban
1879 // fix "lib/stdlib.php:1348: Warning[2]: Compilation failed: unmatched parentheses at offset 2"
1880 //   not reproducable other than on sf.net, but this seems to fix it.
1881 //
1882 // Revision 1.223  2004/12/18 16:49:29  rurban
1883 // fix RPC for !USE_PATH_INFO, add debugging helper
1884 //
1885 // Revision 1.222  2004/12/17 16:40:45  rurban
1886 // add not yet used url helper
1887 //
1888 // Revision 1.221  2004/12/06 19:49:58  rurban
1889 // enable action=remove which is undoable and seeable in RecentChanges: ADODB ony for now.
1890 // renamed delete_page to purge_page.
1891 // enable action=edit&version=-1 to force creation of a new version.
1892 // added BABYCART_PATH config
1893 // fixed magiqc in adodb.inc.php
1894 // and some more docs
1895 //
1896 // Revision 1.220  2004/11/30 17:47:41  rurban
1897 // added mt_srand, check for native isa
1898 //
1899 // Revision 1.219  2004/11/26 18:39:02  rurban
1900 // new regex search parser and SQL backends (90% complete, glob and pcre backends missing)
1901 //
1902 // Revision 1.218  2004/11/25 08:28:48  rurban
1903 // support exclude
1904 //
1905 // Revision 1.217  2004/11/16 17:31:03  rurban
1906 // re-enable old block markup conversion
1907 //
1908 // Revision 1.216  2004/11/11 18:31:26  rurban
1909 // add simple backtrace on such general failures to get at least an idea where
1910 //
1911 // Revision 1.215  2004/11/11 14:34:12  rurban
1912 // minor clarifications
1913 //
1914 // Revision 1.214  2004/11/11 11:01:20  rurban
1915 // fix loadPhpExtension
1916 //
1917 // Revision 1.213  2004/11/01 10:43:57  rurban
1918 // seperate PassUser methods into seperate dir (memory usage)
1919 // fix WikiUser (old) overlarge data session
1920 // remove wikidb arg from various page class methods, use global ->_dbi instead
1921 // ...
1922 //
1923 // Revision 1.212  2004/10/22 09:15:39  rurban
1924 // Alert::show has no arg anymore
1925 //
1926 // Revision 1.211  2004/10/22 09:05:11  rurban
1927 // added longer_timeout (HttpClient)
1928 // fixed warning
1929 //
1930 // Revision 1.210  2004/10/14 21:06:02  rurban
1931 // fix dumphtml with USE_PATH_INFO (again). fix some PageList refs
1932 //
1933 // Revision 1.209  2004/10/14 19:19:34  rurban
1934 // loadsave: check if the dumped file will be accessible from outside.
1935 // and some other minor fixes. (cvsclient native not yet ready)
1936 //
1937 // Revision 1.208  2004/10/12 13:13:20  rurban
1938 // php5 compatibility (5.0.1 ok)
1939 //
1940 // Revision 1.207  2004/09/26 12:21:40  rurban
1941 // removed old log entries.
1942 // added persistent start_debug on internal links and DEBUG
1943 // added isExternalReferrer (not yet used)
1944 //
1945 // Revision 1.206  2004/09/25 16:28:36  rurban
1946 // added to TOC, firstNWordsOfContent is now plugin compatible, added extractSection
1947 //
1948 // Revision 1.205  2004/09/23 13:59:35  rurban
1949 // Before removing a page display a sample of 100 words.
1950 //
1951 // Revision 1.204  2004/09/17 13:19:15  rurban
1952 // fix LinkPhpwikiURL bug reported in http://phpwiki.sourceforge.net/phpwiki/KnownBugs
1953 // by SteveBennett.
1954 //
1955 // Revision 1.203  2004/09/16 08:00:52  rurban
1956 // just some comments
1957 //
1958 // Revision 1.202  2004/09/14 10:11:44  rurban
1959 // start 2nd Id with ...Plugin2
1960 //
1961 // Revision 1.201  2004/09/14 10:06:42  rurban
1962 // generate iterated plugin ids, set plugin span id also
1963 //
1964 // Revision 1.200  2004/08/05 17:34:26  rurban
1965 // move require to sortby branch
1966 //
1967 // Revision 1.199  2004/08/05 10:38:15  rurban
1968 // fix Bug #993692:  Making Snapshots or Backups doesn't work anymore
1969 // in CVS version.
1970 //
1971 // Revision 1.198  2004/07/02 10:30:36  rurban
1972 // always disable getimagesize for < php-4.3 with external png's
1973 //
1974 // Revision 1.197  2004/07/02 09:55:58  rurban
1975 // more stability fixes: new DISABLE_GETIMAGESIZE if your php crashes when loading LinkIcons: failing getimagesize in old phps; blockparser stabilized
1976 //
1977 // Revision 1.196  2004/07/01 08:51:22  rurban
1978 // dumphtml: added exclude, print pagename before processing
1979 //
1980 // Revision 1.195  2004/06/29 08:52:22  rurban
1981 // Use ...version() $need_content argument in WikiDB also:
1982 // To reduce the memory footprint for larger sets of pagelists,
1983 // we don't cache the content (only true or false) and
1984 // we purge the pagedata (_cached_html) also.
1985 // _cached_html is only cached for the current pagename.
1986 // => Vastly improved page existance check, ACL check, ...
1987 //
1988 // Now only PagedList info=content or size needs the whole content, esp. if sortable.
1989 //
1990 // Revision 1.194  2004/06/29 06:48:04  rurban
1991 // Improve LDAP auth and GROUP_LDAP membership:
1992 //   no error message on false password,
1993 //   added two new config vars: LDAP_OU_USERS and LDAP_OU_GROUP with GROUP_METHOD=LDAP
1994 //   fixed two group queries (this -> user)
1995 // stdlib: ConvertOldMarkup still flawed
1996 //
1997 // Revision 1.193  2004/06/28 13:27:03  rurban
1998 // CreateToc disabled for old markup and Apache2 only
1999 //
2000 // Revision 1.192  2004/06/28 12:47:43  rurban
2001 // skip if non-DEBUG and old markup with CreateToc
2002 //
2003 // Revision 1.191  2004/06/25 14:31:56  rurban
2004 // avoid debug_skip warning
2005 //
2006 // Revision 1.190  2004/06/25 14:29:20  rurban
2007 // WikiGroup refactoring:
2008 //   global group attached to user, code for not_current user.
2009 //   improved helpers for special groups (avoid double invocations)
2010 // new experimental config option ENABLE_XHTML_XML (fails with IE, and document.write())
2011 // fixed a XHTML validation error on userprefs.tmpl
2012 //
2013 // Revision 1.189  2004/06/20 09:45:35  rurban
2014 // php5 isa fix (wrong strtolower)
2015 //
2016 // Revision 1.188  2004/06/16 10:38:58  rurban
2017 // Disallow refernces in calls if the declaration is a reference
2018 // ("allow_call_time_pass_reference clean").
2019 //   PhpWiki is now allow_call_time_pass_reference = Off clean,
2020 //   but several external libraries may not.
2021 //   In detail these libs look to be affected (not tested):
2022 //   * Pear_DB odbc
2023 //   * adodb oracle
2024 //
2025 // Revision 1.187  2004/06/14 11:31:37  rurban
2026 // renamed global $Theme to $WikiTheme (gforge nameclash)
2027 // inherit PageList default options from PageList
2028 //   default sortby=pagename
2029 // use options in PageList_Selectable (limit, sortby, ...)
2030 // added action revert, with button at action=diff
2031 // added option regex to WikiAdminSearchReplace
2032 //
2033 // Revision 1.186  2004/06/13 13:54:25  rurban
2034 // Catch fatals on the four dump calls (as file and zip, as html and mimified)
2035 // FoafViewer: Check against external requirements, instead of fatal.
2036 // Change output for xhtmldumps: using file:// urls to the local fs.
2037 // Catch SOAP fatal by checking for GOOGLE_LICENSE_KEY
2038 // Import GOOGLE_LICENSE_KEY and FORTUNE_DIR from config.ini.
2039 //
2040 // Revision 1.185  2004/06/11 09:07:30  rurban
2041 // support theme-specific LinkIconAttr: front or after or none
2042 //
2043 // Revision 1.184  2004/06/04 20:32:53  rurban
2044 // Several locale related improvements suggested by Pierrick Meignen
2045 // LDAP fix by John Cole
2046 // reanable admin check without ENABLE_PAGEPERM in the admin plugins
2047 //
2048 // Revision 1.183  2004/06/01 10:22:56  rurban
2049 // added url_get_contents() used in XmlParser and elsewhere
2050 //
2051 // Revision 1.182  2004/05/25 12:40:48  rurban
2052 // trim the pagename
2053 //
2054 // Revision 1.181  2004/05/25 10:18:44  rurban
2055 // Check for UTF-8 URLs; Internet Explorer produces these if you
2056 // type non-ASCII chars in the URL bar or follow unescaped links.
2057 // Fixes sf.net bug #953949
2058 // src: languages/Language.php:checkTitleEncoding() from mediawiki
2059 //
2060 // Revision 1.180  2004/05/18 16:23:39  rurban
2061 // rename split_pagename to SplitPagename
2062 //
2063 // Revision 1.179  2004/05/18 16:18:37  rurban
2064 // AutoSplit at subpage seperators
2065 // RssFeed stability fix for empty feeds or broken connections
2066 //
2067 // Revision 1.178  2004/05/12 10:49:55  rurban
2068 // require_once fix for those libs which are loaded before FileFinder and
2069 //   its automatic include_path fix, and where require_once doesn't grok
2070 //   dirname(__FILE__) != './lib'
2071 // upgrade fix with PearDB
2072 // navbar.tmpl: remove spaces for IE &nbsp; button alignment
2073 //
2074 // Revision 1.177  2004/05/08 14:06:12  rurban
2075 // new support for inlined image attributes: [image.jpg size=50x30 align=right]
2076 // minor stability and portability fixes
2077 //
2078 // Revision 1.176  2004/05/08 11:25:15  rurban
2079 // php-4.0.4 fixes
2080 //
2081 // Revision 1.175  2004/05/06 17:30:38  rurban
2082 // CategoryGroup: oops, dos2unix eol
2083 // improved phpwiki_version:
2084 //   pre -= .0001 (1.3.10pre: 1030.099)
2085 //   -p1 += .001 (1.3.9-p1: 1030.091)
2086 // improved InstallTable for mysql and generic SQL versions and all newer tables so far.
2087 // abstracted more ADODB/PearDB methods for action=upgrade stuff:
2088 //   backend->backendType(), backend->database(),
2089 //   backend->listOfFields(),
2090 //   backend->listOfTables(),
2091 //
2092 // Revision 1.174  2004/05/06 12:02:05  rurban
2093 // fix sf.net bug#949002: [ Link | ] assertion
2094 //
2095 // Revision 1.173  2004/05/03 15:00:31  rurban
2096 // added more database upgrading: session.sess_ip, page.id autp_increment
2097 //
2098 // Revision 1.172  2004/04/26 20:44:34  rurban
2099 // locking table specific for better databases
2100 //
2101 // Revision 1.171  2004/04/19 23:13:03  zorloc
2102 // Connect the rest of PhpWiki to the IniConfig system.  Also the keyword regular expression is not a config setting
2103 //
2104 // Revision 1.170  2004/04/19 18:27:45  rurban
2105 // Prevent from some PHP5 warnings (ref args, no :: object init)
2106 //   php5 runs now through, just one wrong XmlElement object init missing
2107 // Removed unneccesary UpgradeUser lines
2108 // Changed WikiLink to omit version if current (RecentChanges)
2109 //
2110 // Revision 1.169  2004/04/15 21:29:48  rurban
2111 // allow [0] with new markup: link to page "0"
2112 //
2113 // Revision 1.168  2004/04/10 02:30:49  rurban
2114 // Fixed gettext problem with VIRTUAL_PATH scripts (Windows only probably)
2115 // Fixed "cannot setlocale..." (sf.net problem)
2116 //
2117 // Revision 1.167  2004/04/02 15:06:55  rurban
2118 // fixed a nasty ADODB_mysql session update bug
2119 // improved UserPreferences layout (tabled hints)
2120 // fixed UserPreferences auth handling
2121 // improved auth stability
2122 // improved old cookie handling: fixed deletion of old cookies with paths
2123 //
2124 // Revision 1.166  2004/04/01 15:57:10  rurban
2125 // simplified Sidebar theme: table, not absolute css positioning
2126 // added the new box methods.
2127 // remaining problems: large left margin, how to override _autosplitWikiWords in Template only
2128 //
2129 // Revision 1.165  2004/03/24 19:39:03  rurban
2130 // php5 workaround code (plus some interim debugging code in XmlElement)
2131 //   php5 doesn't work yet with the current XmlElement class constructors,
2132 //   WikiUserNew does work better than php4.
2133 // rewrote WikiUserNew user upgrading to ease php5 update
2134 // fixed pref handling in WikiUserNew
2135 // added Email Notification
2136 // added simple Email verification
2137 // removed emailVerify userpref subclass: just a email property
2138 // changed pref binary storage layout: numarray => hash of non default values
2139 // print optimize message only if really done.
2140 // forced new cookie policy: delete pref cookies, use only WIKI_ID as plain string.
2141 //   prefs should be stored in db or homepage, besides the current session.
2142 //
2143 // Revision 1.164  2004/03/18 21:41:09  rurban
2144 // fixed sqlite support
2145 // WikiUserNew: PHP5 fixes: don't assign $this (untested)
2146 //
2147 // Revision 1.163  2004/03/17 18:41:49  rurban
2148 // just reformatting
2149 //
2150 // Revision 1.162  2004/03/16 15:43:08  rurban
2151 // make fileSet sortable to please PageList
2152 //
2153 // Revision 1.161  2004/03/12 15:48:07  rurban
2154 // fixed explodePageList: wrong sortby argument order in UnfoldSubpages
2155 // simplified lib/stdlib.php:explodePageList
2156 //
2157 // Revision 1.160  2004/02/28 21:14:08  rurban
2158 // generally more PHPDOC docs
2159 //   see http://xarch.tu-graz.ac.at/home/rurban/phpwiki/xref/
2160 // fxied WikiUserNew pref handling: empty theme not stored, save only
2161 //   changed prefs, sql prefs improved, fixed password update,
2162 //   removed REPLACE sql (dangerous)
2163 // moved gettext init after the locale was guessed
2164 // + some minor changes
2165 //
2166
2167 // (c-file-style: "gnu")
2168 // Local Variables:
2169 // mode: php
2170 // tab-width: 8
2171 // c-basic-offset: 4
2172 // c-hanging-comment-ender-p: nil
2173 // indent-tabs-mode: nil
2174 // End:   
2175 ?>