]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/stdlib.php
new support for inlined image attributes: [image.jpg size=50x30 align=right]
[SourceForge/phpwiki.git] / lib / stdlib.php
1 <?php //rcs_id('$Id: stdlib.php,v 1.177 2004-05-08 14:06:12 rurban Exp $');
2
3 /*
4   Standard functions for Wiki functionality
5     WikiURL($pagename, $args, $get_abs_url)
6     IconForLink($protocol_or_url)
7     LinkURL($url, $linktext)
8     LinkImage($url, $alt)
9
10     SplitQueryArgs ($query_args)
11     LinkPhpwikiURL($url, $text)
12     ConvertOldMarkup($content)
13     
14     class Stack { push($item), pop(), cnt(), top() }
15
16     split_pagename ($page)
17     NoSuchRevision ($request, $page, $version)
18     TimezoneOffset ($time, $no_colon)
19     Iso8601DateTime ($time)
20     Rfc2822DateTime ($time)
21     CTime ($time)
22     __printf ($fmt)
23     __sprintf ($fmt)
24     __vsprintf ($fmt, $args)
25     better_srand($seed = '')
26     count_all($arg)
27     isSubPage($pagename)
28     subPageSlice($pagename, $pos)
29     explodePageList($input, $perm = false)
30
31   function: LinkInterWikiLink($link, $linktext)
32   moved to: lib/interwiki.php
33   function: linkExistingWikiWord($wikiword, $linktext, $version)
34   moved to: lib/Theme.php
35   function: LinkUnknownWikiWord($wikiword, $linktext)
36   moved to: lib/Theme.php
37   function: UpdateRecentChanges($dbi, $pagename, $isnewpage) 
38   gone see: lib/plugin/RecentChanges.php
39 */
40
41 define('MAX_PAGENAME_LENGTH', 100);
42
43             
44 /**
45  * Convert string to a valid XML identifier.
46  *
47  * XML 1.0 identifiers are of the form: [A-Za-z][A-Za-z0-9:_.-]*
48  *
49  * We would like to have, e.g. named anchors within wiki pages
50  * names like "Table of Contents" --- clearly not a valid XML
51  * fragment identifier.
52  *
53  * This function implements a one-to-one map from {any string}
54  * to {valid XML identifiers}.
55  *
56  * It does this by
57  * converting all bytes not in [A-Za-z0-9:_-],
58  * and any leading byte not in [A-Za-z] to 'xbb.',
59  * where 'bb' is the hexadecimal representation of the
60  * character.
61  *
62  * As a special case, the empty string is converted to 'empty.'
63  *
64  * @param string $str
65  * @return string
66  */
67 function MangleXmlIdentifier($str) {
68     if (!$str)
69         return 'empty.';
70     
71     return preg_replace('/[^-_:A-Za-z0-9]|(?<=^)[^A-Za-z]/e',
72                         "'x' . sprintf('%02x', ord('\\0')) . '.'",
73                         $str);
74 }
75
76 function UnMangleXmlIdentifier($str) {
77     if ($str == 'empty.')
78         return '';
79     return preg_replace('/x(\w\w)\./e',
80                         "sprintf('%c', hex('\\0'))",
81                         $str);
82 }
83
84 /**
85  * Generates a valid URL for a given Wiki pagename.
86  * @param mixed $pagename If a string this will be the name of the Wiki page to link to.
87  *                        If a WikiDB_Page object function will extract the name to link to.
88  *                        If a WikiDB_PageRevision object function will extract the name to link to.
89  * @param array $args 
90  * @param boolean $get_abs_url Default value is false.
91  * @return string The absolute URL to the page passed as $pagename.
92  */
93 function WikiURL($pagename, $args = '', $get_abs_url = false) {
94     $anchor = false;
95     
96     if (is_object($pagename)) {
97         if (isa($pagename, 'WikiDB_Page')) {
98             $pagename = $pagename->getName();
99         }
100         elseif (isa($pagename, 'WikiDB_PageRevision')) {
101             $page = $pagename->getPage();
102             $args['version'] = $pagename->getVersion();
103             $pagename = $page->getName();
104         }
105         elseif (isa($pagename, 'WikiPageName')) {
106             $anchor = $pagename->anchor;
107             $pagename = $pagename->name;
108         } else { // php5
109             $anchor = $pagename->anchor;
110             $pagename = $pagename->name;
111         }
112     }
113     
114     if (is_array($args)) {
115         $enc_args = array();
116         foreach  ($args as $key => $val) {
117             if (!is_array($val)) // ugly hack for getURLtoSelf() which also takes POST vars
118               $enc_args[] = urlencode($key) . '=' . urlencode($val);
119         }
120         $args = join('&', $enc_args);
121     }
122
123     if (USE_PATH_INFO) {
124         $url = $get_abs_url ? SERVER_URL . VIRTUAL_PATH . "/" : "";
125         $url .= preg_replace('/%2f/i', '/', rawurlencode($pagename));
126         if ($args)
127             $url .= "?$args";
128     }
129     else {
130         $url = $get_abs_url ? SERVER_URL . SCRIPT_NAME : basename(SCRIPT_NAME);
131         $url .= "?pagename=" . rawurlencode($pagename);
132         if ($args)
133             $url .= "&$args";
134     }
135     if ($anchor)
136         $url .= "#" . MangleXmlIdentifier($anchor);
137     return $url;
138 }
139
140 /** Convert relative URL to absolute URL.
141  *
142  * This converts a relative URL to one of PhpWiki's support files
143  * to an absolute one.
144  *
145  * @param string $url
146  * @return string Absolute URL
147  */
148 function AbsoluteURL ($url) {
149     if (preg_match('/^https?:/', $url))
150         return $url;
151     if ($url[0] != '/') {
152         $base = USE_PATH_INFO ? VIRTUAL_PATH : dirname(SCRIPT_NAME);
153         while ($base != '/' and substr($url, 0, 3) == "../") {
154             $url = substr($url, 3);
155             $base = dirname($base);
156         }
157         if ($base != '/')
158             $base .= '/';
159         $url = $base . $url;
160     }
161     return SERVER_URL . $url;
162 }
163
164 /**
165  * Generates icon in front of links.
166  *
167  * @param string $protocol_or_url URL or protocol to determine which icon to use.
168  *
169  * @return HtmlElement HtmlElement object that contains data to create img link to
170  * icon for use with url or protocol passed to the function. False if no img to be
171  * displayed.
172  */
173 function IconForLink($protocol_or_url) {
174     global $Theme;
175     if (0 and $filename_suffix == false) {
176         // display apache style icon for file type instead of protocol icon
177         // - archive: unix:gz,bz2,tgz,tar,z; mac:dmg,dmgz,bin,img,cpt,sit; pc:zip;
178         // - document: html, htm, text, txt, rtf, pdf, doc
179         // - non-inlined image: jpg,jpeg,png,gif,tiff,tif,swf,pict,psd,eps,ps
180         // - audio: mp3,mp2,aiff,aif,au
181         // - multimedia: mpeg,mpg,mov,qt
182     } else {
183         list ($proto) = explode(':', $protocol_or_url, 2);
184         $src = $Theme->getLinkIconURL($proto);
185         if ($src)
186             return HTML::img(array('src' => $src, 'alt' => "", 'class' => 'linkicon', 'border' => 0));
187         else
188             return false;
189     }
190 }
191
192 /**
193  * Glue icon in front of text.
194  *
195  * @param string $protocol_or_url Protocol or URL.  Used to determine the
196  * proper icon.
197  * @param string $text The text.
198  * @return XmlContent.
199  */
200 function PossiblyGlueIconToText($proto_or_url, $text) {
201     global $request;
202     if (! $request->getPref('noLinkIcons')) {
203         $icon = IconForLink($proto_or_url);
204         if ($icon) {
205             if (!is_object($text)) {
206                 preg_match('/^\s*(\S*)(.*?)\s*$/', $text, $m);
207                 list (, $first_word, $tail) = $m;
208             }
209             else {
210                 $first_word = $text;
211                 $tail = false;
212             }
213             
214             $text = HTML::span(array('style' => 'white-space: nowrap'),
215                                $icon, $first_word);
216             if ($tail)
217                 $text = HTML($text, $tail);
218         }
219     }
220     return $text;
221 }
222
223 /**
224  * Determines if the url passed to function is safe, by detecting if the characters
225  * '<', '>', or '"' are present.
226  *
227  * @param string $url URL to check for unsafe characters.
228  * @return boolean True if same, false else.
229  */
230 function IsSafeURL($url) {
231     return !ereg('[<>"]', $url);
232 }
233
234 /**
235  * Generates an HtmlElement object to store data for a link.
236  *
237  * @param string $url URL that the link will point to.
238  * @param string $linktext Text to be displayed as link.
239  * @return HtmlElement HtmlElement object that contains data to construct an html link.
240  */
241 function LinkURL($url, $linktext = '') {
242     // FIXME: Is this needed (or sufficient?)
243     if(! IsSafeURL($url)) {
244         $link = HTML::strong(HTML::u(array('class' => 'baduri'),
245                                      _("BAD URL -- remove all of <, >, \"")));
246     }
247     else {
248         if (!$linktext)
249             $linktext = preg_replace("/mailto:/A", "", $url);
250         
251         $link = HTML::a(array('href' => $url),
252                         PossiblyGlueIconToText($url, $linktext));
253         
254     }
255     $link->setAttr('class', $linktext ? 'namedurl' : 'rawurl');
256     return $link;
257 }
258
259
260 function LinkImage($url, $alt = false) {
261     // FIXME: Is this needed (or sufficient?)
262     if(! IsSafeURL($url)) {
263         $link = HTML::strong(HTML::u(array('class' => 'baduri'),
264                                      _("BAD URL -- remove all of <, >, \"")));
265     } else {
266         // support new syntax: [image.jpg size=50% border=n]
267         $arr = split(' ',$url);
268         if (count($arr) > 1) {
269             $url = $arr[0];
270         }
271         if (empty($alt)) $alt = basename($url);
272         $link = HTML::img(array('src' => $url, 'alt' => $alt));
273         if (count($arr) > 1) {
274             array_shift($arr);
275             foreach ($arr as $attr) {
276                 if (preg_match('/^size=(\d+%)$/',$attr,$m)) {
277                     $link->setAttr('width',$m[1]);
278                     $link->setAttr('height',$m[1]);
279                 }
280                 if (preg_match('/^size=(\d+)x(\d+)$/',$attr,$m)) {
281                     $link->setAttr('width',$m[1]);
282                     $link->setAttr('height',$m[2]);
283                 }
284                 if (preg_match('/^border=(\d+)$/',$attr,$m))
285                     $link->setAttr('border',$m[1]);
286                 if (preg_match('/^align=(\w+)$/',$attr,$m))
287                     $link->setAttr('align',$m[1]);
288                 if (preg_match('/^hspace=(\d+)$/',$attr,$m))
289                     $link->setAttr('hspace',$m[1]);
290                 if (preg_match('/^vspace=(\d+)$/',$attr,$m))
291                     $link->setAttr('vspace',$m[1]);
292             }
293         }
294     }
295     $link->setAttr('class', 'inlineimage');
296     return $link;
297 }
298
299
300
301 class Stack {
302     var $items = array();
303     var $size = 0;
304     // var in php5.0.0.rc1 deprecated
305
306     function push($item) {
307         $this->items[$this->size] = $item;
308         $this->size++;
309         return true;
310     }  
311     
312     function pop() {
313         if ($this->size == 0) {
314             return false; // stack is empty
315         }  
316         $this->size--;
317         return $this->items[$this->size];
318     }  
319     
320     function cnt() {
321         return $this->size;
322     }  
323     
324     function top() {
325         if($this->size)
326             return $this->items[$this->size - 1];
327         else
328             return '';
329     }
330     
331 }  
332 // end class definition
333
334 function SplitQueryArgs ($query_args = '') 
335 {
336     $split_args = split('&', $query_args);
337     $args = array();
338     while (list($key, $val) = each($split_args))
339         if (preg_match('/^ ([^=]+) =? (.*) /x', $val, $m))
340             $args[$m[1]] = $m[2];
341     return $args;
342 }
343
344 function LinkPhpwikiURL($url, $text = '', $basepage) {
345     $args = array();
346     
347     if (!preg_match('/^ phpwiki: ([^?]*) [?]? (.*) $/x', $url, $m)) {
348         return HTML::strong(array('class' => 'rawurl'),
349                             HTML::u(array('class' => 'baduri'),
350                                     _("BAD phpwiki: URL")));
351     }
352
353     if ($m[1])
354         $pagename = urldecode($m[1]);
355     $qargs = $m[2];
356     
357     if (empty($pagename) &&
358         preg_match('/^(diff|edit|links|info)=([^&]+)$/', $qargs, $m)) {
359         // Convert old style links (to not break diff links in
360         // RecentChanges).
361         $pagename = urldecode($m[2]);
362         $args = array("action" => $m[1]);
363     }
364     else {
365         $args = SplitQueryArgs($qargs);
366     }
367
368     if (empty($pagename))
369         $pagename = $GLOBALS['request']->getArg('pagename');
370
371     if (isset($args['action']) && $args['action'] == 'browse')
372         unset($args['action']);
373     
374     /*FIXME:
375       if (empty($args['action']))
376       $class = 'wikilink';
377       else if (is_safe_action($args['action']))
378       $class = 'wikiaction';
379     */
380     if (empty($args['action']) || is_safe_action($args['action']))
381         $class = 'wikiaction';
382     else {
383         // Don't allow administrative links on unlocked pages.
384         $dbi = $GLOBALS['request']->getDbh();
385         $page = $dbi->getPage($basepage);
386         if (!$page->get('locked'))
387             return HTML::span(array('class' => 'wikiunsafe'),
388                               HTML::u(_("Lock page to enable link")));
389         $class = 'wikiadmin';
390     }
391     
392     if (!$text)
393         $text = HTML::span(array('class' => 'rawurl'), $url);
394
395     $wikipage = new WikiPageName($pagename);
396     if (!$wikipage->isValid()) {
397         global $Theme;
398         return $Theme->linkBadWikiWord($wikipage, $url);
399     }
400     
401     return HTML::a(array('href'  => WikiURL($pagename, $args),
402                          'class' => $class),
403                    $text);
404 }
405
406 /**
407  * A class to assist in parsing wiki pagenames.
408  *
409  * Now with subpages and anchors, parsing and passing around
410  * pagenames is more complicated.  This should help.
411  */
412 class WikiPageName
413 {
414     /** Short name for page.
415      *
416      * This is the value of $name passed to the constructor.
417      * (For use, e.g. as a default label for links to the page.)
418      */
419     var $shortName;
420
421     /** The full page name.
422      *
423      * This is the full name of the page (without anchor).
424      */
425     var $name;
426     
427     /** The anchor.
428      *
429      * This is the referenced anchor within the page, or the empty string.
430      */
431     var $anchor;
432     
433     /** Constructor
434      *
435      * @param mixed $name Page name.
436      * WikiDB_Page, WikiDB_PageRevision, or string.
437      * This can be a relative subpage name (like '/SubPage'),
438      * or can be the empty string to refer to the $basename.
439      *
440      * @param string $anchor For links to anchors in page.
441      *
442      * @param mixed $basename Page name from which to interpret
443      * relative or other non-fully-specified page names.
444      */
445     function WikiPageName($name, $basename=false, $anchor=false) {
446         if (is_string($name)) {
447             $this->shortName = $name;
448         
449             if ($name == '' or $name[0] == SUBPAGE_SEPARATOR) {
450                 if ($basename)
451                     $name = $this->_pagename($basename) . $name;
452                 else
453                     $name = $this->_normalize_bad_pagename($name);
454             }
455         }
456         else {
457             $name = $this->_pagename($name);
458             $this->shortName = $name;
459         }
460
461         $this->name = $this->_check($name);
462         $this->anchor = (string)$anchor;
463     }
464
465     function getParent() {
466         $name = $this->name;
467         if (!($tail = strrchr($name, SUBPAGE_SEPARATOR)))
468             return false;
469         return substr($name, 0, -strlen($tail));
470     }
471
472     function isValid($strict = false) {
473         if ($strict)
474             return !isset($this->_errors);
475         return (is_string($this->name) and $this->name != '');
476     }
477
478     function getWarnings() {
479         $warnings = array();
480         if (isset($this->_warnings))
481             $warnings = array_merge($warnings, $this->_warnings);
482         if (isset($this->_errors))
483             $warnings = array_merge($warnings, $this->_errors);
484         if (!$warnings)
485             return false;
486         
487         return sprintf(_("'%s': Bad page name: %s"),
488                        $this->shortName, join(', ', $warnings));
489     }
490     
491     function _pagename($page) {
492         if (isa($page, 'WikiDB_Page'))
493             return $page->getName();
494         elseif (isa($page, 'WikiDB_PageRevision'))
495             return $page->getPageName();
496         elseif (isa($page, 'WikiPageName'))
497             return $page->name;
498         if (!is_string($page)) {
499             trigger_error(sprintf("Non-string pagename '%s' (%s)(%s)",
500                                   $page, gettype($page), get_class($page)),
501                           E_USER_NOTICE);
502         }
503         //assert(is_string($page));
504         return $page;
505     }
506
507     function _normalize_bad_pagename($name) {
508         trigger_error("Bad pagename: " . $name, E_USER_WARNING);
509
510         // Punt...  You really shouldn't get here.
511         if (empty($name)) {
512             global $request;
513             return $request->getArg('pagename');
514         }
515         assert($name[0] == SUBPAGE_SEPARATOR);
516         return substr($name, 1);
517     }
518
519
520     function _check($pagename) {
521         // Compress internal white-space to single space character.
522         $pagename = preg_replace('/[\s\xa0]+/', ' ', $orig = $pagename);
523         if ($pagename != $orig)
524             $this->_warnings[] = _("White space converted to single space");
525     
526         // Delete any control characters.
527         $pagename = preg_replace('/[\x00-\x1f\x7f\x80-\x9f]/', '', $orig = $pagename);
528         if ($pagename != $orig)
529             $this->_errors[] = _("Control characters not allowed");
530
531         // Strip leading and trailing white-space.
532         $pagename = trim($pagename);
533
534         $orig = $pagename;
535         while ($pagename and $pagename[0] == SUBPAGE_SEPARATOR)
536             $pagename = substr($pagename, 1);
537         if ($pagename != $orig)
538             $this->_errors[] = sprintf(_("Leading %s not allowed"), SUBPAGE_SEPARATOR);
539
540         if (preg_match('/[:;]/', $pagename))
541             $this->_warnings[] = _("';' and ':' in pagenames are deprecated");
542         
543         if (strlen($pagename) > MAX_PAGENAME_LENGTH) {
544             $pagename = substr($pagename, 0, MAX_PAGENAME_LENGTH);
545             $this->_errors[] = _("too long");
546         }
547         
548
549         if ($pagename == '.' or $pagename == '..') {
550             $this->_errors[] = sprintf(_("illegal pagename"), $pagename);
551             $pagename = '';
552         }
553         
554         return $pagename;
555     }
556 }
557
558 /**
559  * Convert old page markup to new-style markup.
560  *
561  * @param string $text Old-style wiki markup.
562  *
563  * @param string $markup_type
564  * One of: <dl>
565  * <dt><code>"block"</code>  <dd>Convert all markup.
566  * <dt><code>"inline"</code> <dd>Convert only inline markup.
567  * <dt><code>"links"</code>  <dd>Convert only link markup.
568  * </dl>
569  *
570  * @return string New-style wiki markup.
571  *
572  * @bugs Footnotes don't work quite as before (esp if there are
573  *   multiple references to the same footnote.  But close enough,
574  *   probably for now....
575  */
576 function ConvertOldMarkup ($text, $markup_type = "block") {
577
578     static $subs;
579     static $block_re;
580     
581     if (empty($subs)) {
582         /*****************************************************************
583          * Conversions for inline markup:
584          */
585
586         // escape tilde's
587         $orig[] = '/~/';
588         $repl[] = '~~';
589
590         // escape escaped brackets
591         $orig[] = '/\[\[/';
592         $repl[] = '~[';
593
594         // change ! escapes to ~'s.
595         global $WikiNameRegexp, $request;
596         //include_once('lib/interwiki.php');
597         $map = getInterwikiMap();
598         $bang_esc[] = "(?:" . ALLOWED_PROTOCOLS . "):[^\s<>\[\]\"'()]*[^\s<>\[\]\"'(),.?]";
599         $bang_esc[] = $map->getRegexp() . ":[^\\s.,;?()]+"; // FIXME: is this really needed?
600         $bang_esc[] = $WikiNameRegexp;
601         $orig[] = '/!((?:' . join(')|(', $bang_esc) . '))/';
602         $repl[] = '~\\1';
603
604         $subs["links"] = array($orig, $repl);
605
606         // Escape '<'s
607         //$orig[] = '/<(?!\?plugin)|(?<!^)</m';
608         //$repl[] = '~<';
609         
610         // Convert footnote references.
611         $orig[] = '/(?<=.)(?<!~)\[\s*(\d+)\s*\]/m';
612         $repl[] = '#[|ftnt_ref_\\1]<sup>~[[\\1|#ftnt_\\1]~]</sup>';
613
614         // Convert old style emphases to HTML style emphasis.
615         $orig[] = '/__(.*?)__/';
616         $repl[] = '<strong>\\1</strong>';
617         $orig[] = "/''(.*?)''/";
618         $repl[] = '<em>\\1</em>';
619
620         // Escape nestled markup.
621         $orig[] = '/^(?<=^|\s)[=_](?=\S)|(?<=\S)[=_*](?=\s|$)/m';
622         $repl[] = '~\\0';
623         
624         // in old markup headings only allowed at beginning of line
625         $orig[] = '/!/';
626         $repl[] = '~!';
627
628         $subs["inline"] = array($orig, $repl);
629
630         /*****************************************************************
631          * Patterns which match block markup constructs which take
632          * special handling...
633          */
634
635         // Indented blocks
636         $blockpats[] = '[ \t]+\S(?:.*\s*\n[ \t]+\S)*';
637
638         // Tables
639         $blockpats[] = '\|(?:.*\n\|)*';
640
641         // List items
642         $blockpats[] = '[#*;]*(?:[*#]|;.*?:)';
643
644         // Footnote definitions
645         $blockpats[] = '\[\s*(\d+)\s*\]';
646
647         // Plugins
648         $blockpats[] = '<\?plugin(?:-form)?\b.*\?>\s*$';
649
650         // Section Title
651         $blockpats[] = '!{1,3}[^!]';
652
653         $block_re = ( '/\A((?:.|\n)*?)(^(?:'
654                       . join("|", $blockpats)
655                       . ').*$)\n?/m' );
656         
657     }
658     
659     if ($markup_type != "block") {
660         list ($orig, $repl) = $subs[$markup_type];
661         return preg_replace($orig, $repl, $text);
662     }
663     else {
664         list ($orig, $repl) = $subs['inline'];
665         $out = '';
666         while (preg_match($block_re, $text, $m)) {
667             $text = substr($text, strlen($m[0]));
668             list (,$leading_text, $block) = $m;
669             $suffix = "\n";
670             
671             if (strchr(" \t", $block[0])) {
672                 // Indented block
673                 $prefix = "<pre>\n";
674                 $suffix = "\n</pre>\n";
675             }
676             elseif ($block[0] == '|') {
677                 // Old-style table
678                 $prefix = "<?plugin OldStyleTable\n";
679                 $suffix = "\n?>\n";
680             }
681             elseif (strchr("#*;", $block[0])) {
682                 // Old-style list item
683                 preg_match('/^([#*;]*)([*#]|;.*?:) */', $block, $m);
684                 list (,$ind,$bullet) = $m;
685                 $block = substr($block, strlen($m[0]));
686                 
687                 $indent = str_repeat('     ', strlen($ind));
688                 if ($bullet[0] == ';') {
689                     //$term = ltrim(substr($bullet, 1));
690                     //return $indent . $term . "\n" . $indent . '     ';
691                     $prefix = $ind . $bullet;
692                 }
693                 else
694                     $prefix = $indent . $bullet . ' ';
695             }
696             elseif ($block[0] == '[') {
697                 // Footnote definition
698                 preg_match('/^\[\s*(\d+)\s*\]/', $block, $m);
699                 $footnum = $m[1];
700                 $block = substr($block, strlen($m[0]));
701                 $prefix = "#[|ftnt_${footnum}]~[[${footnum}|#ftnt_ref_${footnum}]~] ";
702             }
703             elseif ($block[0] == '<') {
704                 // Plugin.
705                 // HACK: no inline markup...
706                 $prefix = $block;
707                 $block = '';
708             }
709             elseif ($block[0] == '!') {
710                 // Section heading
711                 preg_match('/^!{1,3}/', $block, $m);
712                 $prefix = $m[0];
713                 $block = substr($block, strlen($m[0]));
714             }
715             else {
716                 // AAck!
717                 assert(0);
718             }
719
720             $out .= ( preg_replace($orig, $repl, $leading_text)
721                       . $prefix
722                       . preg_replace($orig, $repl, $block)
723                       . $suffix );
724         }
725         return $out . preg_replace($orig, $repl, $text);
726     }
727 }
728
729
730 /**
731  * Expand tabs in string.
732  *
733  * Converts all tabs to (the appropriate number of) spaces.
734  *
735  * @param string $str
736  * @param integer $tab_width
737  * @return string
738  */
739 function expand_tabs($str, $tab_width = 8) {
740     $split = split("\t", $str);
741     $tail = array_pop($split);
742     $expanded = "\n";
743     foreach ($split as $hunk) {
744         $expanded .= $hunk;
745         $pos = strlen(strrchr($expanded, "\n")) - 1;
746         $expanded .= str_repeat(" ", ($tab_width - $pos % $tab_width));
747     }
748     return substr($expanded, 1) . $tail;
749 }
750
751 /**
752  * Split WikiWords in page names.
753  *
754  * It has been deemed useful to split WikiWords (into "Wiki Words") in
755  * places like page titles. This is rumored to help search engines
756  * quite a bit.
757  *
758  * @param $page string The page name.
759  *
760  * @return string The split name.
761  */
762 function split_pagename ($page) {
763     
764     if (preg_match("/\s/", $page))
765         return $page;           // Already split --- don't split any more.
766     
767     // FIXME: this algorithm is Anglo-centric.
768     static $RE;
769     if (!isset($RE)) {
770         // This mess splits between a lower-case letter followed by
771         // either an upper-case or a numeral; except that it wont
772         // split the prefixes 'Mc', 'De', or 'Di' off of their tails.
773         $RE[] = '/([[:lower:]])((?<!Mc|De|Di)[[:upper:]]|\d)/';
774         // This the single-letter words 'I' and 'A' from any following
775         // capitalized words.
776         $sep = preg_quote(SUBPAGE_SEPARATOR, '/');
777         $RE[] = "/(?<= |${sep}|^)([AI])([[:upper:]][[:lower:]])/";
778         // Split numerals from following letters.
779         $RE[] = '/(\d)([[:alpha:]])/';
780         //TODO: Split at subpage seperators. TBD in Theme.php
781         //$RE[] = "/(${sep})([^${sep}]+)/";
782         
783         foreach ($RE as $key)
784             $RE[$key] = pcre_fix_posix_classes($key);
785     }
786
787     foreach ($RE as $regexp) {
788         $page = preg_replace($regexp, '\\1 \\2', $page);
789     }
790     return $page;
791 }
792
793 function NoSuchRevision (&$request, $page, $version) {
794     $html = HTML(HTML::h2(_("Revision Not Found")),
795                  HTML::p(fmt("I'm sorry.  Version %d of %s is not in the database.",
796                              $version, WikiLink($page, 'auto'))));
797     include_once('lib/Template.php');
798     GeneratePage($html, _("Bad Version"), $page->getCurrentRevision());
799     $request->finish();
800 }
801
802
803 /**
804  * Get time offset for local time zone.
805  *
806  * @param $time time_t Get offset for this time. Default: now.
807  * @param $no_colon boolean Don't put colon between hours and minutes.
808  * @return string Offset as a string in the format +HH:MM.
809  */
810 function TimezoneOffset ($time = false, $no_colon = false) {
811     if ($time === false)
812         $time = time();
813     $secs = date('Z', $time);
814
815     if ($secs < 0) {
816         $sign = '-';
817         $secs = -$secs;
818     }
819     else {
820         $sign = '+';
821     }
822     $colon = $no_colon ? '' : ':';
823     $mins = intval(($secs + 30) / 60);
824     return sprintf("%s%02d%s%02d",
825                    $sign, $mins / 60, $colon, $mins % 60);
826 }
827
828
829 /**
830  * Format time in ISO-8601 format.
831  *
832  * @param $time time_t Time.  Default: now.
833  * @return string Date and time in ISO-8601 format.
834  */
835 function Iso8601DateTime ($time = false) {
836     if ($time === false)
837         $time = time();
838     $tzoff = TimezoneOffset($time);
839     $date  = date('Y-m-d', $time);
840     $time  = date('H:i:s', $time);
841     return $date . 'T' . $time . $tzoff;
842 }
843
844 /**
845  * Format time in RFC-2822 format.
846  *
847  * @param $time time_t Time.  Default: now.
848  * @return string Date and time in RFC-2822 format.
849  */
850 function Rfc2822DateTime ($time = false) {
851     if ($time === false)
852         $time = time();
853     return date('D, j M Y H:i:s ', $time) . TimezoneOffset($time, 'no colon');
854 }
855
856 /**
857  * Format time in RFC-1123 format.
858  *
859  * @param $time time_t Time.  Default: now.
860  * @return string Date and time in RFC-1123 format.
861  */
862 function Rfc1123DateTime ($time = false) {
863     if ($time === false)
864         $time = time();
865     return gmdate('D, d M Y H:i:s \G\M\T', $time);
866 }
867
868 /** Parse date in RFC-1123 format.
869  *
870  * According to RFC 1123 we must accept dates in the following
871  * formats:
872  *
873  *   Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
874  *   Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
875  *   Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
876  *
877  * (Though we're only allowed to generate dates in the first format.)
878  */
879 function ParseRfc1123DateTime ($timestr) {
880     $timestr = trim($timestr);
881     if (preg_match('/^ \w{3},\s* (\d{1,2}) \s* (\w{3}) \s* (\d{4}) \s*'
882                    .'(\d\d):(\d\d):(\d\d) \s* GMT $/ix',
883                    $timestr, $m)) {
884         list(, $mday, $mon, $year, $hh, $mm, $ss) = $m;
885     }
886     elseif (preg_match('/^ \w+,\s* (\d{1,2})-(\w{3})-(\d{2}|\d{4}) \s*'
887                        .'(\d\d):(\d\d):(\d\d) \s* GMT $/ix',
888                        $timestr, $m)) {
889         list(, $mday, $mon, $year, $hh, $mm, $ss) = $m;
890         if ($year < 70) $year += 2000;
891         elseif ($year < 100) $year += 1900;
892     }
893     elseif (preg_match('/^\w+\s* (\w{3}) \s* (\d{1,2}) \s*'
894                        .'(\d\d):(\d\d):(\d\d) \s* (\d{4})$/ix',
895                        $timestr, $m)) {
896         list(, $mon, $mday, $hh, $mm, $ss, $year) = $m;
897     }
898     else {
899         // Parse failed.
900         return false;
901     }
902
903     $time = strtotime("$mday $mon $year ${hh}:${mm}:${ss} GMT");
904     if ($time == -1)
905         return false;           // failed
906     return $time;
907 }
908
909 /**
910  * Format time to standard 'ctime' format.
911  *
912  * @param $time time_t Time.  Default: now.
913  * @return string Date and time.
914  */
915 function CTime ($time = false)
916 {
917     if ($time === false)
918         $time = time();
919     return date("D M j H:i:s Y", $time);
920 }
921
922
923 /**
924  * Format number as kilobytes or bytes.
925  * Short format is used for PageList
926  * Long format is used in PageInfo
927  *
928  * @param $bytes       int.  Default: 0.
929  * @param $longformat  bool. Default: false.
930  * @return class FormattedText (XmlElement.php).
931  */
932 function ByteFormatter ($bytes = 0, $longformat = false) {
933     if ($bytes < 0)
934         return fmt("-???");
935     if ($bytes < 1024) {
936         if (! $longformat)
937             $size = fmt("%s b", $bytes);
938         else
939             $size = fmt("%s bytes", $bytes);
940     }
941     else {
942         $kb = round($bytes / 1024, 1);
943         if (! $longformat)
944             $size = fmt("%s k", $kb);
945         else
946             $size = fmt("%s Kb (%s bytes)", $kb, $bytes);
947     }
948     return $size;
949 }
950
951 /**
952  * Internationalized printf.
953  *
954  * This is essentially the same as PHP's built-in printf
955  * with the following exceptions:
956  * <ol>
957  * <li> It passes the format string through gettext().
958  * <li> It supports the argument reordering extensions.
959  * </ol>
960  *
961  * Example:
962  *
963  * In php code, use:
964  * <pre>
965  *    __printf("Differences between versions %s and %s of %s",
966  *             $new_link, $old_link, $page_link);
967  * </pre>
968  *
969  * Then in locale/po/de.po, one can reorder the printf arguments:
970  *
971  * <pre>
972  *    msgid "Differences between %s and %s of %s."
973  *    msgstr "Der Unterschiedsergebnis von %3$s, zwischen %1$s und %2$s."
974  * </pre>
975  *
976  * (Note that while PHP tries to expand $vars within double-quotes,
977  * the values in msgstr undergo no such expansion, so the '$'s
978  * okay...)
979  *
980  * One shouldn't use reordered arguments in the default format string.
981  * Backslashes in the default string would be necessary to escape the
982  * '$'s, and they'll cause all kinds of trouble....
983  */ 
984 function __printf ($fmt) {
985     $args = func_get_args();
986     array_shift($args);
987     echo __vsprintf($fmt, $args);
988 }
989
990 /**
991  * Internationalized sprintf.
992  *
993  * This is essentially the same as PHP's built-in printf with the
994  * following exceptions:
995  *
996  * <ol>
997  * <li> It passes the format string through gettext().
998  * <li> It supports the argument reordering extensions.
999  * </ol>
1000  *
1001  * @see __printf
1002  */ 
1003 function __sprintf ($fmt) {
1004     $args = func_get_args();
1005     array_shift($args);
1006     return __vsprintf($fmt, $args);
1007 }
1008
1009 /**
1010  * Internationalized vsprintf.
1011  *
1012  * This is essentially the same as PHP's built-in printf with the
1013  * following exceptions:
1014  *
1015  * <ol>
1016  * <li> It passes the format string through gettext().
1017  * <li> It supports the argument reordering extensions.
1018  * </ol>
1019  *
1020  * @see __printf
1021  */ 
1022 function __vsprintf ($fmt, $args) {
1023     $fmt = gettext($fmt);
1024     // PHP's sprintf doesn't support variable with specifiers,
1025     // like sprintf("%*s", 10, "x"); --- so we won't either.
1026     
1027     if (preg_match_all('/(?<!%)%(\d+)\$/x', $fmt, $m)) {
1028         // Format string has '%2$s' style argument reordering.
1029         // PHP doesn't support this.
1030         if (preg_match('/(?<!%)%[- ]?\d*[^- \d$]/x', $fmt))
1031             // literal variable name substitution only to keep locale
1032             // strings uncluttered
1033             trigger_error(sprintf(_("Can't mix '%s' with '%s' type format strings"),
1034                                   '%1\$s','%s'), E_USER_WARNING); //php+locale error
1035         
1036         $fmt = preg_replace('/(?<!%)%\d+\$/x', '%', $fmt);
1037         $newargs = array();
1038         
1039         // Reorder arguments appropriately.
1040         foreach($m[1] as $argnum) {
1041             if ($argnum < 1 || $argnum > count($args))
1042                 trigger_error(sprintf(_("%s: argument index out of range"), 
1043                                       $argnum), E_USER_WARNING);
1044             $newargs[] = $args[$argnum - 1];
1045         }
1046         $args = $newargs;
1047     }
1048     
1049     // Not all PHP's have vsprintf, so...
1050     array_unshift($args, $fmt);
1051     return call_user_func_array('sprintf', $args);
1052 }
1053
1054 function file_mtime ($filename) {
1055     if ($stat = stat($filename))
1056         return $stat[9];
1057     else 
1058         return false;
1059 }
1060
1061 function sort_file_mtime ($a, $b) {
1062     $ma = file_mtime($a);
1063     $mb = file_mtime($b);
1064     if (!$ma or !$mb or $ma == $mb) return 0;
1065     return ($ma > $mb) ? -1 : 1;
1066 }
1067
1068 class fileSet {
1069     /**
1070      * Build an array in $this->_fileList of files from $dirname.
1071      * Subdirectories are not traversed.
1072      *
1073      * (This was a function LoadDir in lib/loadsave.php)
1074      * See also http://www.php.net/manual/en/function.readdir.php
1075      */
1076     function getFiles($exclude=false,$sortby=false,$limit=false) {
1077         $list = $this->_fileList;
1078         if ($sortby) {
1079             switch (Pagelist::sortby($sortby,'db')) {
1080             case 'pagename ASC': break;
1081             case 'pagename DESC': 
1082                 $list = array_reverse($list); 
1083                 break;
1084             case 'mtime ASC': 
1085                 usort($list,'sort_file_mtime'); 
1086                 break;
1087             case 'mtime DESC': 
1088                 usort($list,'sort_file_mtime');
1089                 $list = array_reverse($list); 
1090                 break;
1091             }
1092         }
1093         if ($limit)
1094             return array_splice($list,0,$limit);
1095         return $list;
1096     }
1097
1098     function _filenameSelector($filename) {
1099         if (! $this->_pattern)
1100             return true;
1101         else {
1102             return glob_match ($this->_pattern, $filename, $this->_case);
1103         }
1104     }
1105
1106     function fileSet($directory, $filepattern = false) {
1107         $this->_fileList = array();
1108         $this->_pattern = $filepattern;
1109         $this->_case = !isWindows();
1110         $this->_pathsep = '/';
1111
1112         if (empty($directory)) {
1113             trigger_error(sprintf(_("%s is empty."), 'directoryname'),
1114                           E_USER_NOTICE);
1115             return; // early return
1116         }
1117
1118         @ $dir_handle = opendir($dir=$directory);
1119         if (empty($dir_handle)) {
1120             trigger_error(sprintf(_("Unable to open directory '%s' for reading"),
1121                                   $dir), E_USER_NOTICE);
1122             return; // early return
1123         }
1124
1125         while ($filename = readdir($dir_handle)) {
1126             if ($filename[0] == '.' || filetype($dir . $this->_pathsep . $filename) != 'file')
1127                 continue;
1128             if ($this->_filenameSelector($filename)) {
1129                 array_push($this->_fileList, "$filename");
1130                 //trigger_error(sprintf(_("found file %s"), $filename),
1131                 //                      E_USER_NOTICE); //debugging
1132             }
1133         }
1134         closedir($dir_handle);
1135     }
1136 };
1137
1138 // File globbing
1139
1140 // expands a list containing regex's to its matching entries
1141 class ListRegexExpand {
1142     var $match, $list, $index, $case_sensitive;
1143     function ListRegexExpand (&$list, $match, $case_sensitive = true) {
1144         $this->match = str_replace('/','\/',$match);
1145         $this->list = &$list;
1146         $this->case_sensitive = $case_sensitive;        
1147         //$this->index = false;
1148     }
1149     function listMatchCallback ($item, $key) {
1150         if (preg_match('/' . $this->match . ($this->case_sensitive ? '/' : '/i'), $item)) {
1151             unset($this->list[$this->index]);
1152             $this->list[] = $item;
1153         }
1154     }
1155     function expandRegex ($index, &$pages) {
1156         $this->index = $index;
1157         array_walk($pages, array($this, 'listMatchCallback'));
1158         return $this->list;
1159     }
1160 }
1161
1162 // convert fileglob to regex style
1163 function glob_to_pcre ($glob) {
1164     $re = preg_replace('/\./', '\\.', $glob);
1165     $re = preg_replace(array('/\*/','/\?/'), array('.*','.'), $glob);
1166     if (!preg_match('/^[\?\*]/',$glob))
1167         $re = '^' . $re;
1168     if (!preg_match('/[\?\*]$/',$glob))
1169         $re = $re . '$';
1170     return $re;
1171 }
1172
1173 function glob_match ($glob, $against, $case_sensitive = true) {
1174     return preg_match('/' . glob_to_pcre($glob) . ($case_sensitive ? '/' : '/i'), $against);
1175 }
1176
1177 function explodeList($input, $allnames, $glob_style = true, $case_sensitive = true) {
1178     $list = explode(',',$input);
1179     // expand wildcards from list of $allnames
1180     if (preg_match('/[\?\*]/',$input)) {
1181         // Optimizing loop invariants:
1182         // http://phplens.com/lens/php-book/optimizing-debugging-php.php
1183         for ($i = 0, $max = sizeof($list); $i < $max; $i++) {
1184             $f = $list[$i];
1185             if (preg_match('/[\?\*]/',$f)) {
1186                 reset($allnames);
1187                 $expand = new ListRegexExpand($list, $glob_style ? glob_to_pcre($f) : $f, $case_sensitive);
1188                 $expand->expandRegex($i, $allnames);
1189             }
1190         }
1191     }
1192     return $list;
1193 }
1194
1195 // echo implode(":",explodeList("Test*",array("xx","Test1","Test2")));
1196 function explodePageList($input, $perm=false, $sortby='pagename', $limit=false) {
1197     include_once("lib/PageList.php");
1198     return PageList::explodePageList($input,$perm,$sortby,$limit);
1199 }
1200
1201 // Class introspections
1202
1203 /** Determine whether object is of a specified type.
1204  *
1205  * @param $object object An object.
1206  * @param $class string Class name.
1207  * @return bool True iff $object is a $class
1208  * or a sub-type of $class. 
1209  */
1210 function isa ($object, $class) {
1211     $lclass = strtolower($class);
1212
1213     return is_object($object)
1214         && ( get_class($object) == strtolower($lclass)
1215              || is_subclass_of($object, $lclass) );
1216 }
1217
1218 /** Determine whether (possible) object has method.
1219  *
1220  * @param $object mixed Object
1221  * @param $method string Method name
1222  * @return bool True iff $object is an object with has method $method.
1223  */
1224 function can ($object, $method) {
1225     return is_object($object) && method_exists($object, strtolower($method));
1226 }
1227
1228 /** Determine whether a function is okay to use.
1229  *
1230  * Some providers (e.g. Lycos) disable some of PHP functions for
1231  * "security reasons."  This makes those functions, of course,
1232  * unusable, despite the fact the function_exists() says they
1233  * exist.
1234  *
1235  * This function test to see if a function exists and is not
1236  * disallowed by PHP's disable_functions config setting.
1237  *
1238  * @param string $function_name  Function name
1239  * @return bool  True iff function can be used.
1240  */
1241 function function_usable($function_name) {
1242     static $disabled;
1243     if (!is_array($disabled)) {
1244         $disabled = array();
1245         // Use get_cfg_var since ini_get() is one of the disabled functions
1246         // (on Lycos, at least.)
1247         $split = preg_split('/\s*,\s*/', trim(get_cfg_var('disable_functions')));
1248         foreach ($split as $f)
1249             $disabled[strtolower($f)] = true;
1250     }
1251
1252     return ( function_exists($function_name)
1253              and ! isset($disabled[strtolower($function_name)])
1254              );
1255 }
1256     
1257     
1258 /** Hash a value.
1259  *
1260  * This is used for generating ETags.
1261  */
1262 function hash ($x) {
1263     if (is_scalar($x)) {
1264         return $x;
1265     }
1266     elseif (is_array($x)) {            
1267         ksort($x);
1268         return md5(serialize($x));
1269     }
1270     elseif (is_object($x)) {
1271         return $x->hash();
1272     }
1273     trigger_error("Can't hash $x", E_USER_ERROR);
1274 }
1275
1276     
1277 /**
1278  * Seed the random number generator.
1279  *
1280  * better_srand() ensures the randomizer is seeded only once.
1281  * 
1282  * How random do you want it? See:
1283  * http://www.php.net/manual/en/function.srand.php
1284  * http://www.php.net/manual/en/function.mt-srand.php
1285  */
1286 function better_srand($seed = '') {
1287     static $wascalled = FALSE;
1288     if (!$wascalled) {
1289         $seed = $seed === '' ? (double) microtime() * 1000000 : $seed;
1290         srand($seed);
1291         $wascalled = TRUE;
1292         //trigger_error("new random seed", E_USER_NOTICE); //debugging
1293     }
1294 }
1295
1296 /**
1297  * Recursively count all non-empty elements 
1298  * in array of any dimension or mixed - i.e. 
1299  * array('1' => 2, '2' => array('1' => 3, '2' => 4))
1300  * See http://www.php.net/manual/en/function.count.php
1301  */
1302 function count_all($arg) {
1303     // skip if argument is empty
1304     if ($arg) {
1305         //print_r($arg); //debugging
1306         $count = 0;
1307         // not an array, return 1 (base case) 
1308         if(!is_array($arg))
1309             return 1;
1310         // else call recursively for all elements $arg
1311         foreach($arg as $key => $val)
1312             $count += count_all($val);
1313         return $count;
1314     }
1315 }
1316
1317 function isSubPage($pagename) {
1318     return (strstr($pagename, SUBPAGE_SEPARATOR));
1319 }
1320
1321 function subPageSlice($pagename, $pos) {
1322     $pages = explode(SUBPAGE_SEPARATOR,$pagename);
1323     $pages = array_slice($pages,$pos,1);
1324     return $pages[0];
1325 }
1326
1327 /**
1328  * Alert
1329  *
1330  * Class for "popping up" and alert box.  (Except that right now, it doesn't
1331  * pop up...)
1332  *
1333  * FIXME:
1334  * This is a hackish and needs to be refactored.  However it would be nice to
1335  * unify all the different methods we use for showing Alerts and Dialogs.
1336  * (E.g. "Page deleted", login form, ...)
1337  */
1338 class Alert {
1339     /** Constructor
1340      *
1341      * @param object $request
1342      * @param mixed $head  Header ("title") for alert box.
1343      * @param mixed $body  The text in the alert box.
1344      * @param hash $buttons  An array mapping button labels to URLs.
1345      *    The default is a single "Okay" button pointing to $request->getURLtoSelf().
1346      */
1347     function Alert($head, $body, $buttons=false) {
1348         if ($buttons === false)
1349             $buttons = array();
1350
1351         $this->_tokens = array('HEADER' => $head, 'CONTENT' => $body);
1352         $this->_buttons = $buttons;
1353     }
1354
1355     /**
1356      * Show the alert box.
1357      */
1358     function show(&$request) {
1359         global $request;
1360
1361         $tokens = $this->_tokens;
1362         $tokens['BUTTONS'] = $this->_getButtons();
1363         
1364         $request->discardOutput();
1365         $tmpl = new Template('dialog', $request, $tokens);
1366         $tmpl->printXML();
1367         $request->finish();
1368     }
1369
1370
1371     function _getButtons() {
1372         global $request;
1373
1374         $buttons = $this->_buttons;
1375         if (!$buttons)
1376             $buttons = array(_("Okay") => $request->getURLtoSelf());
1377         
1378         global $Theme;
1379         foreach ($buttons as $label => $url)
1380             print "$label $url\n";
1381             $out[] = $Theme->makeButton($label, $url, 'wikiaction');
1382         return new XmlContent($out);
1383     }
1384 }
1385
1386 // 1.3.8     => 1030.08
1387 // 1.3.9-p1  => 1030.091
1388 // 1.3.10pre => 1030.099
1389 function phpwiki_version() {
1390     static $PHPWIKI_VERSION;
1391     if (!isset($PHPWIKI_VERSION)) {
1392         $arr = explode('.',preg_replace('/\D+$/','', PHPWIKI_VERSION)); // remove the pre
1393         $arr[2] = preg_replace('/\.+/','.',preg_replace('/\D/','.',$arr[2]));
1394         $PHPWIKI_VERSION = $arr[0]*1000 + $arr[1]*10 + 0.01*$arr[2];
1395         if (substr(PHPWIKI_VERSION,-3,3) == 'pre')
1396             $PHPWIKI_VERSION -= 0.001;
1397     }
1398     return $PHPWIKI_VERSION;
1399 }
1400
1401 function isWikiWord($word) {
1402     global $WikiNameRegexp;
1403     //or preg_match('/\A' . $WikiNameRegexp . '\z/', $word) ??
1404     return preg_match("/^$WikiNameRegexp\$/",$word);
1405 }
1406
1407 // needed to store serialized objects-values only (perm, pref)
1408 function obj2hash ($obj, $exclude = false, $fields = false) {
1409     $a = array();
1410     if (! $fields ) $fields = get_object_vars($obj);
1411     foreach ($fields as $key => $val) {
1412         if (is_array($exclude)) {
1413             if (in_array($key,$exclude)) continue;
1414         }
1415         $a[$key] = $val;
1416     }
1417     return $a;
1418 }
1419
1420 // $Log: not supported by cvs2svn $
1421 // Revision 1.176  2004/05/08 11:25:15  rurban
1422 // php-4.0.4 fixes
1423 //
1424 // Revision 1.175  2004/05/06 17:30:38  rurban
1425 // CategoryGroup: oops, dos2unix eol
1426 // improved phpwiki_version:
1427 //   pre -= .0001 (1.3.10pre: 1030.099)
1428 //   -p1 += .001 (1.3.9-p1: 1030.091)
1429 // improved InstallTable for mysql and generic SQL versions and all newer tables so far.
1430 // abstracted more ADODB/PearDB methods for action=upgrade stuff:
1431 //   backend->backendType(), backend->database(),
1432 //   backend->listOfFields(),
1433 //   backend->listOfTables(),
1434 //
1435 // Revision 1.174  2004/05/06 12:02:05  rurban
1436 // fix sf.net bug#949002: [ Link | ] assertion
1437 //
1438 // Revision 1.173  2004/05/03 15:00:31  rurban
1439 // added more database upgrading: session.sess_ip, page.id autp_increment
1440 //
1441 // Revision 1.172  2004/04/26 20:44:34  rurban
1442 // locking table specific for better databases
1443 //
1444 // Revision 1.171  2004/04/19 23:13:03  zorloc
1445 // Connect the rest of PhpWiki to the IniConfig system.  Also the keyword regular expression is not a config setting
1446 //
1447 // Revision 1.170  2004/04/19 18:27:45  rurban
1448 // Prevent from some PHP5 warnings (ref args, no :: object init)
1449 //   php5 runs now through, just one wrong XmlElement object init missing
1450 // Removed unneccesary UpgradeUser lines
1451 // Changed WikiLink to omit version if current (RecentChanges)
1452 //
1453 // Revision 1.169  2004/04/15 21:29:48  rurban
1454 // allow [0] with new markup: link to page "0"
1455 //
1456 // Revision 1.168  2004/04/10 02:30:49  rurban
1457 // Fixed gettext problem with VIRTUAL_PATH scripts (Windows only probably)
1458 // Fixed "cannot setlocale..." (sf.net problem)
1459 //
1460 // Revision 1.167  2004/04/02 15:06:55  rurban
1461 // fixed a nasty ADODB_mysql session update bug
1462 // improved UserPreferences layout (tabled hints)
1463 // fixed UserPreferences auth handling
1464 // improved auth stability
1465 // improved old cookie handling: fixed deletion of old cookies with paths
1466 //
1467 // Revision 1.166  2004/04/01 15:57:10  rurban
1468 // simplified Sidebar theme: table, not absolute css positioning
1469 // added the new box methods.
1470 // remaining problems: large left margin, how to override _autosplitWikiWords in Template only
1471 //
1472 // Revision 1.165  2004/03/24 19:39:03  rurban
1473 // php5 workaround code (plus some interim debugging code in XmlElement)
1474 //   php5 doesn't work yet with the current XmlElement class constructors,
1475 //   WikiUserNew does work better than php4.
1476 // rewrote WikiUserNew user upgrading to ease php5 update
1477 // fixed pref handling in WikiUserNew
1478 // added Email Notification
1479 // added simple Email verification
1480 // removed emailVerify userpref subclass: just a email property
1481 // changed pref binary storage layout: numarray => hash of non default values
1482 // print optimize message only if really done.
1483 // forced new cookie policy: delete pref cookies, use only WIKI_ID as plain string.
1484 //   prefs should be stored in db or homepage, besides the current session.
1485 //
1486 // Revision 1.164  2004/03/18 21:41:09  rurban
1487 // fixed sqlite support
1488 // WikiUserNew: PHP5 fixes: don't assign $this (untested)
1489 //
1490 // Revision 1.163  2004/03/17 18:41:49  rurban
1491 // just reformatting
1492 //
1493 // Revision 1.162  2004/03/16 15:43:08  rurban
1494 // make fileSet sortable to please PageList
1495 //
1496 // Revision 1.161  2004/03/12 15:48:07  rurban
1497 // fixed explodePageList: wrong sortby argument order in UnfoldSubpages
1498 // simplified lib/stdlib.php:explodePageList
1499 //
1500 // Revision 1.160  2004/02/28 21:14:08  rurban
1501 // generally more PHPDOC docs
1502 //   see http://xarch.tu-graz.ac.at/home/rurban/phpwiki/xref/
1503 // fxied WikiUserNew pref handling: empty theme not stored, save only
1504 //   changed prefs, sql prefs improved, fixed password update,
1505 //   removed REPLACE sql (dangerous)
1506 // moved gettext init after the locale was guessed
1507 // + some minor changes
1508 //
1509 // Revision 1.158  2004/02/19 21:54:17  rurban
1510 // moved initerwiki code to PageType.php
1511 // re-enabled and fixed InlineImages support, now also for InterWiki Urls
1512 //      * [File:my_image.gif] inlines the image,
1513 //      * File:my_image.gif shows a plain inter-wiki link,
1514 //      * [what a pic|File:my_image.gif] shows a named inter-wiki link to the gif
1515 //      * [File:my_image.gif|what a pic] shows a inlimed image linked to the page "what a pic"
1516 //
1517 // Revision 1.157  2004/02/09 03:58:12  rurban
1518 // for now default DB_SESSION to false
1519 // PagePerm:
1520 //   * not existing perms will now query the parent, and not
1521 //     return the default perm
1522 //   * added pagePermissions func which returns the object per page
1523 //   * added getAccessDescription
1524 // WikiUserNew:
1525 //   * added global ->prepare (not yet used) with smart user/pref/member table prefixing.
1526 //   * force init of authdbh in the 2 db classes
1527 // main:
1528 //   * fixed session handling (not triple auth request anymore)
1529 //   * don't store cookie prefs with sessions
1530 // stdlib: global obj2hash helper from _AuthInfo, also needed for PagePerm
1531 //
1532 // Revision 1.156  2004/01/26 09:17:49  rurban
1533 // * changed stored pref representation as before.
1534 //   the array of objects is 1) bigger and 2)
1535 //   less portable. If we would import packed pref
1536 //   objects and the object definition was changed, PHP would fail.
1537 //   This doesn't happen with an simple array of non-default values.
1538 // * use $prefs->retrieve and $prefs->store methods, where retrieve
1539 //   understands the interim format of array of objects also.
1540 // * simplified $prefs->get() and fixed $prefs->set()
1541 // * added $user->_userid and class '_WikiUser' portability functions
1542 // * fixed $user object ->_level upgrading, mostly using sessions.
1543 //   this fixes yesterdays problems with loosing authorization level.
1544 // * fixed WikiUserNew::checkPass to return the _level
1545 // * fixed WikiUserNew::isSignedIn
1546 // * added explodePageList to class PageList, support sortby arg
1547 // * fixed UserPreferences for WikiUserNew
1548 // * fixed WikiPlugin for empty defaults array
1549 // * UnfoldSubpages: added pagename arg, renamed pages arg,
1550 //   removed sort arg, support sortby arg
1551 //
1552 // Revision 1.155  2004/01/25 10:52:22  rurban
1553 // added sortby support to explodePageList() and UnfoldSubpages
1554 // fixes [ 758044 ] Plugin UnfoldSubpages does not sort (includes fix)
1555 //
1556 // Revision 1.154  2004/01/25 03:49:03  rurban
1557 // added isWikiWord() to avoid redundancy
1558 // added check_php_version() to check for older php versions.
1559 //   e.g. object::method calls, ...
1560 //
1561 // Revision 1.153  2003/11/30 18:43:18  carstenklapp
1562 // Fixed careless mistakes in my last optimization commit.
1563 //
1564 // Revision 1.152  2003/11/30 18:20:34  carstenklapp
1565 // Minor code optimization: reduce invariant loops
1566 //
1567 // Revision 1.151  2003/11/29 19:30:01  carstenklapp
1568 // New function ByteFormatter.
1569 //
1570 // Revision 1.150  2003/09/13 22:43:00  carstenklapp
1571 // New preference to hide LinkIcons.
1572 //
1573 // Revision 1.149  2003/03/26 19:37:08  dairiki
1574 // Fix "object to string conversion" bug with external image links.
1575 //
1576 // Revision 1.148  2003/03/25 21:03:02  dairiki
1577 // Cleanup debugging output.
1578 //
1579 // Revision 1.147  2003/03/13 20:17:05  dairiki
1580 // Bug fix: Fix linking of pages whose names contain a hash ('#').
1581 //
1582 // Revision 1.146  2003/03/07 02:46:24  dairiki
1583 // function_usable(): New function.
1584 //
1585 // Revision 1.145  2003/03/04 01:55:05  dairiki
1586 // Fix to ensure absolute URL for logo in RSS recent changes.
1587 //
1588 // Revision 1.144  2003/02/26 00:39:30  dairiki
1589 // Bug fix: for magic PhpWiki URLs, "lock page to enable link" message was
1590 // being displayed at incorrect times.
1591 //
1592 // Revision 1.143  2003/02/26 00:10:26  dairiki
1593 // More/better/different checks for bad page names.
1594 //
1595 // Revision 1.142  2003/02/25 22:19:46  dairiki
1596 // Add some sanity checking for pagenames.
1597 //
1598 // Revision 1.141  2003/02/22 20:49:55  dairiki
1599 // Fixes for "Call-time pass by reference has been deprecated" errors.
1600 //
1601 // Revision 1.140  2003/02/21 23:33:29  dairiki
1602 // Set alt="" on the link icon image tags.
1603 // (See SF bug #675141.)
1604 //
1605 // Revision 1.139  2003/02/21 22:16:27  dairiki
1606 // Get rid of MakeWikiForm, and form-style MagicPhpWikiURLs.
1607 // These have been obsolete for quite awhile (I hope).
1608 //
1609 // Revision 1.138  2003/02/21 04:12:36  dairiki
1610 // WikiPageName: fixes for new cached links.
1611 //
1612 // Alert: new class for displaying alerts.
1613 //
1614 // ExtractWikiPageLinks and friends are now gone.
1615 //
1616 // LinkBracketLink moved to InlineParser.php
1617 //
1618 // Revision 1.137  2003/02/18 23:13:40  dairiki
1619 // Wups again.  Typo fix.
1620 //
1621 // Revision 1.136  2003/02/18 21:52:07  dairiki
1622 // Fix so that one can still link to wiki pages with # in their names.
1623 // (This was made difficult by the introduction of named tags, since
1624 // '[Page #1]' is now a link to anchor '1' in page 'Page'.
1625 //
1626 // Now the ~ escape for page names should work: [Page ~#1].
1627 //
1628 // Revision 1.135  2003/02/18 19:17:04  dairiki
1629 // split_pagename():
1630 //     Bug fix. 'ThisIsABug' was being split to 'This IsA Bug'.
1631 //     Cleanup up subpage splitting code.
1632 //
1633 // Revision 1.134  2003/02/16 19:44:20  dairiki
1634 // New function hash().  This is a helper, primarily for generating
1635 // HTTP ETags.
1636 //
1637 // Revision 1.133  2003/02/16 04:50:09  dairiki
1638 // New functions:
1639 // Rfc1123DateTime(), ParseRfc1123DateTime()
1640 // for converting unix timestamps to and from strings.
1641 //
1642 // These functions produce and grok the time strings
1643 // in the format specified by RFC 2616 for use in HTTP headers
1644 // (like Last-Modified).
1645 //
1646 // Revision 1.132  2003/01/04 22:19:43  carstenklapp
1647 // Bugfix UnfoldSubpages: "Undefined offset: 1" error when plugin invoked
1648 // on a page with no subpages (explodeList(): array 0-based, sizeof 1-based).
1649 //
1650
1651 // (c-file-style: "gnu")
1652 // Local Variables:
1653 // mode: php
1654 // tab-width: 8
1655 // c-basic-offset: 4
1656 // c-hanging-comment-ender-p: nil
1657 // indent-tabs-mode: nil
1658 // End:   
1659 ?>