]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/plugin/SystemInfo.php
Activated Id substitution for Subversion
[SourceForge/phpwiki.git] / lib / plugin / SystemInfo.php
1 <?php // -*-php-*-
2 rcs_id('$Id$');
3 /**
4  Copyright (C) 1999, 2000, 2001, 2002 $ThePhpWikiProgrammingTeam
5
6  This file is part of PhpWiki.
7
8  PhpWiki is free software; you can redistribute it and/or modify
9  it under the terms of the GNU General Public License as published by
10  the Free Software Foundation; either version 2 of the License, or
11  (at your option) any later version.
12
13  PhpWiki is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  GNU General Public License for more details.
17
18  You should have received a copy of the GNU General Public License
19  along with PhpWiki; if not, write to the Free Software
20  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22
23 /**
24  * Usage: <?plugin SystemInfo all ?>
25  *        or <?plugin SystemInfo pagestats cachestats discspace hitstats ?>
26  *        or <?plugin SystemInfo version ?>
27  *        or <?plugin SystemInfo current_theme ?>
28  *        or <?plugin SystemInfo PHPWIKI_DIR ?>
29  *
30  * Provide access to phpwiki's lower level system information.
31  *
32  *   version, charset, pagestats, SERVER_NAME, database, discspace,
33  *   cachestats, userstats, linkstats, accessstats, hitstats,
34  *   revisionstats, interwikilinks, imageextensions, wikiwordregexp,
35  *   availableplugins, downloadurl  or any other predefined CONSTANT
36  *
37  * In spirit to http://www.ecyrd.com/JSPWiki/SystemInfo.jsp
38  *
39  * Done: Some calculations are heavy (~5-8 secs), so we should cache
40  *       the result. In the page or with WikiPluginCached?
41  */
42
43 require_once "lib/WikiPluginCached.php";
44 class WikiPlugin_SystemInfo
45 extends WikiPluginCached
46 {
47     function getPluginType() {
48         return PLUGIN_CACHED_HTML;
49     }
50     function getName() {
51         return _("SystemInfo");
52     }
53
54     function getDescription() {
55         return _("Provides access to PhpWiki's lower level system information.");
56     }
57
58     function getVersion() {
59         return preg_replace("/[Revision: $]/", '',
60                             "\$Revision: 1.23 $");
61     }
62     /* From lib/WikiPlugin.php:
63      * If the plugin can deduce a modification time, or equivalent
64      * sort of tag for it's content, then the plugin should
65      * call $request->appendValidators() with appropriate arguments,
66      * and should override this method to return true.
67      */
68     function managesValidators() {
69         return true;
70     }
71     function getExpire($dbi, $argarray, $request) {
72         return '+1800'; // 30 minutes
73     }
74     function getHtml($dbi, $argarray, $request, $basepage) {
75         $loader = new WikiPluginLoader;
76         return $loader->expandPI('<?plugin SystemInfo '
77                                  . WikiPluginCached::glueArgs($argarray) // all
78                                  . ' ?>', $request, $this, $basepage);
79     }
80     /*
81     function getDefaultArguments() {
82         return array(
83                      'seperator' => ' ', // on multiple args
84                      );
85     }
86     */
87
88     function database() {
89         global $request;
90         $s  = "DATABASE_TYPE: " . DATABASE_TYPE . ", ";
91         switch (DATABASE_TYPE) {
92         case 'SQL':     // pear
93         case 'ADODB':
94         case 'PDO':
95             $dsn = DATABASE_DSN;
96             $s .= "DATABASE BACKEND:" . " ";
97             $s .= (DATABASE_TYPE == 'SQL') ? 'PearDB' : 'ADODB';
98             if (preg_match('/^(\w+):/', $dsn, $m)) {
99                 $backend = $m[1];
100                 $s .= " $backend, ";
101             }
102             $s .= "DATABASE_PREFIX: \"".DATABASE_PREFIX."\", ";
103             break;
104         case 'dba':
105             $s .= "DATABASE_DBA_HANDLER: ".DATABASE_DBA_HANDLER.", ";
106             $s .= "DATABASE_DIRECTORY: \"".DATABASE_DIRECTORY."\", ";
107             break;
108         case 'cvs':
109             $s .= "DATABASE_DIRECTORY: \"".DATABASE_DIRECTORY."\", ";
110             // $s .= "cvs stuff: , ";
111             break;
112         case 'flatfile':
113             $s .= "DATABASE_DIRECTORY: ".DATABASE_DIRECTORY.", ";
114             break;
115         }
116         // hack: suppress error when using sql, so no timeout
117         @$s .= "DATABASE_TIMEOUT: " . DATABASE_TIMEOUT . ", ";
118         return $s;
119     }
120     function cachestats() {
121         global $request;
122         if (! defined('USECACHE') or !USECACHE)
123             return _("no cache used");
124         $dbi =& $this->_dbi;
125         $cache = $dbi->_cache;
126         $s  = _("cached pagedata:") . " " . count($cache->_pagedata_cache);
127         $s .= ", " . _("cached versiondata:");
128         $s .= " " . count($cache->_versiondata_cache);
129         //$s .= ", glv size: " . count($cache->_glv_cache);
130         //$s .= ", cache hits: ?";
131         //$s .= ", cache misses: ?";
132         return $s;
133     }
134     function ExpireParams() {
135         global $ExpireParams;
136         $s  = sprintf(_("Keep up to %d major edits, but keep them no longer than %d days."),
137                       $ExpireParams['major']['keep'],
138                       $ExpireParams['major']['max_age']);
139         $s .= sprintf(_(" Keep up to %d minor edits, but keep them no longer than %d days."),
140                       $ExpireParams['minor']['keep'],
141                       $ExpireParams['minor']['max_age']);
142         $s .= sprintf(_(" Keep the latest contributions of the last %d authors up to %d days."),
143                       $ExpireParams['author']['keep'], $ExpireParams['author']['max_age']);
144         $s .= sprintf(_(" Additionally, try to keep the latest contributions of all authors in the last %d days (even if there are more than %d of them,) but in no case keep more than %d unique author revisions."),
145                       $ExpireParams['author']['min_age'],
146                       $ExpireParams['author']['keep'],
147                       $ExpireParams['author']['max_keep']);
148         return $s;
149     }
150     function pagestats() {
151         global $request;
152         $dbi = $request->getDbh();
153         $s  = sprintf(_("%d pages"), $dbi->numPages(true));
154         $s  .= ", " . sprintf(_("%d not-empty pages"), $dbi->numPages(false));
155         // more bla....
156         // $s  .= ", " . sprintf(_("earliest page from %s"), $earliestdate);
157         // $s  .= ", " . sprintf(_("latest page from %s"), $latestdate);
158         // $s  .= ", " . sprintf(_("latest pagerevision from %s"), $latestrevdate);
159         return $s;
160     }
161     //What kind of link statistics?
162     //  total links in, total links out, mean links per page, ...
163     //  Any useful numbers similar to a VisualWiki interestmap?
164     function linkstats() {
165         $s  = _("not yet");
166         return $s;
167     }
168     // number of homepages: easy
169     // number of anonymous users?
170     //   calc this from accesslog info?
171     // number of anonymous edits?
172     //   easy. related to the view/edit rate in accessstats.
173     function userstats() {
174         global $request;
175         $dbi =& $this->_dbi;
176         $h = 0;
177         $page_iter = $dbi->getAllPages(true);
178         while ($page = $page_iter->next()) {
179             if ($page->isUserPage(true)) // check if the admin is there. if not add him to the authusers.
180                 $h++;
181         }
182         $s  = sprintf(_("%d homepages"), $h);
183         // $s  .= ", " . sprintf(_("%d anonymous users"), $au); // ??
184         // $s  .= ", " . sprintf(_("%d anonymous edits"), $ae); // see recentchanges
185         // $s  .= ", " . sprintf(_("%d authenticated users"), $auth); // users with password set
186         // $s  .= ", " . sprintf(_("%d externally authenticated users"), $extauth); // query AuthDB?
187         return $s;
188     }
189
190     //only from logging info possible. = hitstats per time.
191     // total hits per day/month/year
192     // view/edit rate
193     // TODO: see WhoIsOnline hit stats, and sql accesslogs
194     function accessstats() {
195         $s  = _("not yet");
196         return $s;
197     }
198
199     // numeric array
200     function _stats($hits, $treshold = 10.0) {
201         sort($hits);
202         reset($hits);
203         $n = count($hits);
204         $max = 0; $min = 9999999999999; $sum = 0;
205         foreach ($hits as $h) {
206             $sum += $h;
207             $max = max($h, $max);
208             $min = min($h, $min);
209         }
210         $median_i = (int) $n / 2;
211         if (! ($n / 2))
212             $median = $hits[$median_i];
213         else
214             $median = $hits[$median_i];
215         $treshold = 10;
216         $mintreshold = $max * $treshold / 100.0;   // lower than 10% of the hits
217         reset($hits);
218         $nmin = $hits[0] < $mintreshold ? 1 : 0;
219         while (next($hits) < $mintreshold)
220             $nmin++;
221         $maxtreshold = $max - $mintreshold; // more than 90% of the hits
222         end($hits);
223         $nmax = 1;
224         while (prev($hits) > $maxtreshold)
225             $nmax++;
226         return array('n'     => $n,
227                      'sum'   => $sum,
228                      'min'   => $min,
229                      'max'   => $max,
230                      'mean'  => $sum / $n,
231                      'median'=> $median,
232                      'stddev'=> stddev($hits, $sum),
233                      'treshold'    => $treshold,
234                      'nmin'        => $nmin,
235                      'mintreshold' => $mintreshold,
236                      'nmax'        => $nmax, 
237                      'maxtreshold' => $maxtreshold);
238     }
239
240     // only absolute numbers, not for any time interval. see accessstats
241     //  some useful number derived from the curve of the hit stats.
242     //  total, max, mean, median, stddev;
243     //  %d pages less than 3 hits (<10%)    <10% percent of the leastpopular
244     //  %d pages more than 100 hits (>90%)  >90% percent of the mostpopular
245     function hitstats() {
246         global $request;
247         $dbi =& $this->_dbi;
248         $hits = array();
249         $page_iter = $dbi->getAllPages(true);
250         while ($page = $page_iter->next()) {
251             if (($current = $page->getCurrentRevision())
252                 && (! $current->hasDefaultContents())) {
253                 $hits[] = $page->get('hits');
254             }
255         }
256         $treshold = 10.0;
257         $stats = $this->_stats($hits, $treshold);
258
259         $s  = sprintf(_("total hits: %d"), $stats['sum']);
260         $s .= ", " . sprintf(_("max: %d"), $stats['max']);
261         $s .= ", " . sprintf(_("mean: %2.3f"), $stats['mean']);
262         $s .= ", " . sprintf(_("median: %d"), $stats['median']);
263         $s .= ", " . sprintf(_("stddev: %2.3f"), $stats['stddev']);
264         $s .= "; " . sprintf(_("%d pages with less than %d hits (<%d%%)."),
265                              $stats['nmin'], $stats['mintreshold'], $treshold);
266         $s .= " " . sprintf(_("%d page(s) with more than %d hits (>%d%%)."),
267                             $stats['nmax'], $stats['maxtreshold'], 100 - $treshold);
268         return $s;
269     }
270     /* not yet ready
271      */
272     function revisionstats() {
273         global $request, $LANG;
274
275         include_once("lib/WikiPluginCached.php");
276         $cache = WikiPluginCached::newCache();
277         $id = $cache->generateId('SystemInfo::revisionstats_' . $LANG);
278         $cachedir = 'plugincache';
279         $content = $cache->get($id, $cachedir);
280
281         if (!empty($content))
282             return $content;
283
284         $dbi =& $this->_dbi;
285         $stats = array();
286         $page_iter = $dbi->getAllPages(true);
287         $stats['empty'] = $stats['latest']['major'] = $stats['latest']['minor'] = 0;
288         while ($page = $page_iter->next()) {
289             if (!$page->exists()) {
290                 $stats['empty']++;
291                 continue;
292             }
293             $current = $page->getCurrentRevision();
294             // is the latest revision a major or minor one?
295             //   latest revision: numpages 200 (100%) / major (60%) / minor (40%)
296             if ($current->get('is_minor_edit'))
297                 $stats['latest']['major']++;
298             else
299                 $stats['latest']['minor']++;
300 /*
301             // FIXME: This needs much too long to be acceptable.
302             // overall:
303             //   number of revisions: all (100%) / major (60%) / minor (40%)
304             // revs per page:
305             //   per page: mean 20 / major (60%) / minor (40%)
306             $rev_iter = $page->getAllRevisions();
307             while ($rev = $rev_iter->next()) {
308                 if ($rev->get('is_minor_edit'))
309                     $stats['page']['major']++;
310                 else
311                     $stats['page']['minor']++;
312             }
313             $rev_iter->free();
314             $stats['page']['all'] = $stats['page']['major'] + $stats['page']['minor'];
315             $stats['perpage'][]       = $stats['page']['all'];
316             $stats['perpage_major'][] = $stats['page']['major'];
317             $stats['sum']['all'] += $stats['page']['all'];
318             $stats['sum']['major'] += $stats['page']['major'];
319             $stats['sum']['minor'] += $stats['page']['minor'];
320             $stats['page'] = array();
321 */            
322         }
323         $page_iter->free();
324         $stats['numpages'] = $stats['latest']['major'] + $stats['latest']['minor'];
325         $stats['latest']['major_perc'] = $stats['latest']['major'] * 100.0 / $stats['numpages'];
326         $stats['latest']['minor_perc'] = $stats['latest']['minor'] * 100.0 / $stats['numpages'];
327         $empty = sprintf("empty pages: %d (%02.1f%%) / %d (100%%)\n", 
328                          $stats['empty'], $stats['empty'] * 100.0 / $stats['numpages'],
329                          $stats['numpages']);
330         $latest = sprintf("latest revision: major %d (%02.1f%%) / minor %d (%02.1f%%) / all %d (100%%)\n", 
331                           $stats['latest']['major'], $stats['latest']['major_perc'], 
332                           $stats['latest']['minor'], $stats['latest']['minor_perc'], $stats['numpages']);
333 /*                          
334         $stats['sum']['major_perc'] = $stats['sum']['major'] * 100.0 / $stats['sum']['all'];
335         $stats['sum']['minor_perc'] = $stats['sum']['minor'] * 100.0 / $stats['sum']['all'];
336         $sum = sprintf("number of revisions: major %d (%02.1f%%) / minor %d (%02.1f%%) / all %d (100%%)\n", 
337                        $stats['sum']['major'], $stats['sum']['major_perc'],
338                        $stats['sum']['minor'], $stats['sum']['minor_perc'], $stats['sum']['all']);
339
340         $stats['perpage']       = $this->_stats($stats['perpage']);
341         $stats['perpage_major'] = $this->_stats($stats['perpage_major']);
342         $stats['perpage']['major_perc'] = $stats['perpage_major']['sum'] * 100.0 / $stats['perpage']['sum'];
343         $stats['perpage']['minor_perc'] = 100 - $stats['perpage']['major_perc'];
344         $stats['perpage_minor']['sum']  = $stats['perpage']['sum'] - $stats['perpage_major']['sum'];
345         $stats['perpage_minor']['mean'] = $stats['perpage_minor']['sum'] / ($stats['perpage']['n'] - $stats['perpage_major']['n']);
346         $perpage = sprintf("revisions per page: all %d, mean %02.1f / major %d (%02.1f%%) / minor %d (%02.1f%%)\n", 
347                            $stats['perpage']['sum'], $stats['perpage']['mean'],
348                            $stats['perpage_major']['mean'], $stats['perpage']['major_perc'],
349                            $stats['perpage_minor']['mean'], $stats['perpage']['minor_perc']);
350         $perpage .= sprintf("  %d page(s) with less than %d revisions (<%d%%)\n",
351                             $stats['perpage']['nmin'], $stats['perpage']['maintreshold'], $treshold);
352         $perpage .= sprintf("  %d page(s) with more than %d revisions (>%d%%)\n",
353                             $stats['perpage']['nmax'], $stats['perpage']['maxtreshold'], 100 - $treshold);
354         $content = $empty . $latest . $sum . $perpage;
355 */
356         $content = $empty . $latest;
357
358         // regenerate cache every 30 minutes
359         $cache->save($id, $content, '+1800', $cachedir); 
360         return $content;
361     }
362     // Size of databases/files/cvs are possible plus the known size of the app.
363     // Cache this costly operation. 
364     // Even if the whole plugin call is stored internally, we cache this 
365     // seperately with a seperate key.
366     function discspace() {
367         global $DBParams, $request;
368
369         include_once("lib/WikiPluginCached.php");
370         $cache = WikiPluginCached::newCache();
371         $id = $cache->generateId('SystemInfo::discspace');
372         $cachedir = 'plugincache';
373         $content = $cache->get($id, $cachedir);
374
375         if (empty($content)) {
376             $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : '.';
377             //TODO: windows only (no cygwin)
378             $appsize = `du -s $dir | cut -f1`;
379
380             if (in_array($DBParams['dbtype'], array('SQL', 'ADODB'))) {
381                 //TODO: where is the data is actually stored? see phpMyAdmin
382                 $pagesize = 0;
383             } elseif ($DBParams['dbtype'] == 'dba') {
384                 $pagesize = 0;
385                 $dbdir = $DBParams['directory'];
386                 if ($DBParams['dba_handler'] == 'db3')
387                     $pagesize = filesize($DBParams['directory']
388                                          . "/wiki_pagedb.db3") / 1024;
389                 // if issubdirof($dbdir, $dir) $appsize -= $pagesize;
390             } else { // flatfile, cvs
391                 $dbdir = $DBParams['directory'];
392                 $pagesize = `du -s $dbdir`;
393                 // if issubdirof($dbdir, $dir) $appsize -= $pagesize;
394             }
395             $content = array('appsize' => $appsize,
396                              'pagesize' => $pagesize);
397             // regenerate cache every 30 minutes
398             $cache->save($id, $content, '+1800', $cachedir); 
399         } else {
400             $appsize = $content['appsize'];
401             $pagesize = $content['pagesize'];
402         }
403
404         $s  = sprintf(_("Application size: %d Kb"), $appsize);
405         if ($pagesize)
406             $s  .= ", " . sprintf(_("Pagedata size: %d Kb", $pagesize));
407         return $s;
408     }
409
410     function inlineimages () {
411         return implode(' ', explode('|', INLINE_IMAGES));
412     }
413     function wikinameregexp () {
414         return $GLOBALS['WikiNameRegexp'];
415     }
416     function allowedprotocols () {
417         return implode(' ', explode('|', ALLOWED_PROTOCOLS));
418     }
419     function available_plugins () {
420         $fileset = new FileSet(FindFile('lib/plugin'), '*.php');
421         $list = $fileset->getFiles();
422         natcasesort($list);
423         reset($list);
424         return sprintf(_("Total %d plugins: "), count($list))
425             . implode(', ', array_map(create_function('$f',
426                                                       'return substr($f,0,-4);'),
427                                       $list));
428     }
429     function supported_languages () {
430         $available_languages = listAvailableLanguages();
431         natcasesort($available_languages);
432
433         return sprintf(_("Total of %d languages: "),
434                        count($available_languages))
435             . implode(', ', $available_languages) . ". "
436             . sprintf(_("Current language: '%s'"), $GLOBALS['LANG'])
437             . ((DEFAULT_LANGUAGE != $GLOBALS['LANG'])
438                ? ". " . sprintf(_("Default language: '%s'"), DEFAULT_LANGUAGE)
439                : '');
440     }
441
442     function supported_themes () {
443         global $WikiTheme;
444         $available_themes = listAvailableThemes();
445         natcasesort($available_themes);
446         return sprintf(_("Total of %d themes: "), count($available_themes))
447             . implode(', ',$available_themes) . ". "
448             . sprintf(_("Current theme: '%s'"), $WikiTheme->_name)
449             . ((THEME != $WikiTheme->_name)
450                ? ". " . sprintf(_("Default theme: '%s'"), THEME)
451                : '');
452     }
453
454     function call ($arg, &$availableargs) {
455         if (!empty($availableargs[$arg]))
456             return $availableargs[$arg]();
457         elseif (method_exists($this, $arg)) // any defined SystemInfo->method()
458             return call_user_func_array(array(&$this, $arg), '');
459         elseif (defined($arg) && // any defined constant
460                 !in_array($arg,array('ADMIN_PASSWD','DATABASE_DSN','DBAUTH_AUTH_DSN')))
461             return constant($arg);
462         else
463             return $this->error(sprintf(_("unknown argument '%s' to SystemInfo"), $arg));
464     }
465
466     function run($dbi, $argstr, &$request, $basepage) {
467         // don't parse argstr for name=value pairs. instead we use just 'name'
468         //$args = $this->getArgs($argstr, $request);
469         $this->_dbi =& $dbi;
470         $args['seperator'] = ' ';
471         $availableargs = // name => callback + 0 args
472             array ('appname' => create_function('',"return 'PhpWiki';"),
473                    'version' => create_function('',"return sprintf('%s', PHPWIKI_VERSION);"),
474                    'LANG'    => create_function('','return $GLOBALS["LANG"];'),
475                    'LC_ALL'  => create_function('','return setlocale(LC_ALL, 0);'),
476                    'current_language' => create_function('','return $GLOBALS["LANG"];'),
477                    'system_language' => create_function('','return DEFAULT_LANGUAGE;'),
478                    'current_theme' => create_function('','return $GLOBALS["Theme"]->_name;'),
479                    'system_theme'  => create_function('','return THEME;'),
480                    // more here or as method.
481                    '' => create_function('',"return 'dummy';")
482                    );
483         // split the argument string by any number of commas or space
484         // characters, which include " ", \r, \t, \n and \f
485         $allargs = preg_split("/[\s,]+/", $argstr, -1, PREG_SPLIT_NO_EMPTY);
486         if (in_array('all', $allargs) || in_array('table', $allargs)) {
487             $allargs = array('appname'          => _("Application name"),
488                              'version'          => _("PhpWiki engine version"),
489                              'database'         => _("Database"),
490                              'cachestats'       => _("Cache statistics"),
491                              'pagestats'        => _("Page statistics"),
492                              //'revisionstats'    => _("Page revision statistics"),
493                              //'linkstats'        => _("Link statistics"),
494                              'userstats'        => _("User statistics"),
495                              //'accessstats'      => _("Access statistics"),
496                              'hitstats'         => _("Hit statistics"),
497                              'discspace'        => _("Harddisc usage"),
498                              'expireparams'     => _("Expiry parameters"),
499                              'wikinameregexp'   => _("Wikiname regexp"),
500                              'allowedprotocols' => _("Allowed protocols"),
501                              'inlineimages'     => _("Inline images"),
502                              'available_plugins'   => _("Available plugins"),
503                              'supported_languages' => _("Supported languages"),
504                              'supported_themes'    => _("Supported themes"),
505 //                           '' => _(""),
506                              '' => ""
507                              );
508             $table = HTML::table(array('border' => 1,'cellspacing' => 3,
509                                        'cellpadding' => 3));
510             foreach ($allargs as $arg => $desc) {
511                 if (!$arg)
512                     continue;
513                 if (!$desc)
514                     $desc = _($arg);
515                 $table->pushContent(HTML::tr(HTML::td(HTML::strong($desc . ':')),
516                                              HTML::td(HTML($this->call($arg, $availableargs)))));
517             }
518             return $table;
519         } else {
520             $output = '';
521             foreach ($allargs as $arg) {
522                 $o = $this->call($arg, $availableargs);
523                 if (is_object($o))
524                     return $o;
525                 else
526                     $output .= ($o . $args['seperator']);
527             }
528             // if more than one arg, remove the trailing seperator
529             if ($output) $output = substr($output, 0,
530                                           - strlen($args['seperator']));
531             return HTML($output);
532         }
533     }
534 }
535
536 function median($hits) {
537     sort($hits);
538     reset($hits);
539     $n = count($hits);
540     $median = (int) $n / 2;
541     if (! ($n % 2)) // proper rounding on even length
542         return ($hits[$median] + $hits[$median-1]) * 0.5;
543     else
544         return $hits[$median];
545 }
546
547 function rsum($a, $b) {
548     $a += $b;
549     return $a;
550 }
551 function mean(&$hits, $total = false) {
552     $n = count($hits);
553     if (!$total)
554         $total = array_reduce($hits, 'rsum');
555     return (float) $total / ($n * 1.0);
556 }
557 function gensym($prefix = "_gensym") {
558     $i = 0;
559     while (isset($GLOBALS[$prefix . $i]))
560         $i++;
561     return $prefix . $i;
562 }
563
564 function stddev(&$hits, $total = false) {
565     $n = count($hits);
566     if (!$total) $total = array_reduce($hits, 'rsum');
567     $GLOBALS['mean'] = $total / $n;
568     $r = array_map(create_function('$i', 'global $mean; return ($i-$mean)*($i-$mean);'),
569                    $hits);
570     unset($GLOBALS['mean']);
571     return (float)sqrt(mean($r, $total) * ($n / (float)($n -1)));
572 }
573
574 // $Log: not supported by cvs2svn $
575 // Revision 1.22  2004/12/26 17:10:44  rurban
576 // just docs or whitespace
577 //
578 // Revision 1.21  2004/11/26 18:39:02  rurban
579 // new regex search parser and SQL backends (90% complete, glob and pcre backends missing)
580 //
581 // Revision 1.20  2004/11/20 11:28:49  rurban
582 // fix a yet unused PageList customPageListColumns bug (merge class not decl to _types)
583 // change WantedPages to use PageList
584 // change WantedPages to print the list of referenced pages, not just the count.
585 //   the old version was renamed to WantedPagesOld
586 //   fix and add handling of most standard PageList arguments (limit, exclude, ...)
587 // TODO: pagename sorting, dumb/WantedPagesIter and SQL optimization
588 //
589 // Revision 1.19  2004/06/19 11:49:42  rurban
590 // dont print db passwords
591 //
592 // Revision 1.18  2004/06/14 11:31:39  rurban
593 // renamed global $Theme to $WikiTheme (gforge nameclash)
594 // inherit PageList default options from PageList
595 //   default sortby=pagename
596 // use options in PageList_Selectable (limit, sortby, ...)
597 // added action revert, with button at action=diff
598 // added option regex to WikiAdminSearchReplace
599 //
600 // Revision 1.17  2004/05/08 14:06:13  rurban
601 // new support for inlined image attributes: [image.jpg size=50x30 align=right]
602 // minor stability and portability fixes
603 //
604 // Revision 1.16  2004/05/06 20:30:47  rurban
605 // revert and removed some comments
606 //
607 // Revision 1.15  2004/05/03 11:40:42  rurban
608 // put listAvailableLanguages() and listAvailableThemes() from SystemInfo and
609 // UserPreferences into Themes.php
610 //
611 // Revision 1.14  2004/04/19 23:13:04  zorloc
612 // Connect the rest of PhpWiki to the IniConfig system.  Also the keyword regular expression is not a config setting
613 //
614 // Revision 1.13  2004/04/18 01:11:52  rurban
615 // more numeric pagename fixes.
616 // fixed action=upload with merge conflict warnings.
617 // charset changed from constant to global (dynamic utf-8 switching)
618 //
619 // Revision 1.12  2004/03/14 16:26:21  rurban
620 // copyright line
621 //
622 // Revision 1.11  2004/03/13 19:24:21  rurban
623 // fixed supported_languages() and supported_themes()
624 //
625 // Revision 1.10  2004/03/08 18:17:10  rurban
626 // added more WikiGroup::getMembersOf methods, esp. for special groups
627 // fixed $LDAP_SET_OPTIONS
628 // fixed _AuthInfo group methods
629 //
630 // Revision 1.9  2004/02/17 12:11:36  rurban
631 // added missing 4th basepage arg at plugin->run() to almost all plugins. This caused no harm so far, because it was silently dropped on normal usage. However on plugin internal ->run invocations it failed. (InterWikiSearch, IncludeSiteMap, ...)
632 //
633 // Revision 1.8  2003/02/24 01:36:26  dairiki
634 // Don't use PHPWIKI_DIR unless it's defined.
635 // (Also typo/bugfix in SystemInfo plugin.)
636 //
637 // Revision 1.7  2003/02/22 20:49:56  dairiki
638 // Fixes for "Call-time pass by reference has been deprecated" errors.
639 //
640 // Revision 1.6  2003/02/21 23:01:11  dairiki
641 // Fixes to support new $basepage argument of WikiPlugin::run().
642 //
643 // Revision 1.5  2003/01/18 22:08:01  carstenklapp
644 // Code cleanup:
645 // Reformatting & tabs to spaces;
646 // Added copyleft, getVersion, getDescription, rcs_id.
647 //
648
649 // Local Variables:
650 // mode: php
651 // tab-width: 8
652 // c-basic-offset: 4
653 // c-hanging-comment-ender-p: nil
654 // indent-tabs-mode: nil
655 // End:
656 ?>