4 Copyright (C) 1999, 2000, 2001, 2002 $ThePhpWikiProgrammingTeam
6 This file is part of PhpWiki.
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.
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.
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
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 ?>
30 * Provide access to phpwiki's lower level system information.
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
37 * In spirit to http://www.ecyrd.com/JSPWiki/SystemInfo.jsp
39 * Done: Some calculations are heavy (~5-8 secs), so we should cache
40 * the result. In the page or with WikiPluginCached?
43 require_once "lib/WikiPluginCached.php";
44 class WikiPlugin_SystemInfo
45 extends WikiPluginCached
47 function getPluginType() {
48 return PLUGIN_CACHED_HTML;
51 return _("SystemInfo");
54 function getDescription() {
55 return _("Provides access to PhpWiki's lower level system information.");
58 function getVersion() {
59 return preg_replace("/[Revision: $]/", '',
60 "\$Revision: 1.23 $");
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.
68 function managesValidators() {
71 function getExpire($dbi, $argarray, $request) {
72 return '+1800'; // 30 minutes
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);
81 function getDefaultArguments() {
83 'seperator' => ' ', // on multiple args
90 $s = "DATABASE_TYPE: " . DATABASE_TYPE . ", ";
91 switch (DATABASE_TYPE) {
96 $s .= "DATABASE BACKEND:" . " ";
97 $s .= (DATABASE_TYPE == 'SQL') ? 'PearDB' : 'ADODB';
98 if (preg_match('/^(\w+):/', $dsn, $m)) {
102 $s .= "DATABASE_PREFIX: \"".DATABASE_PREFIX."\", ";
105 $s .= "DATABASE_DBA_HANDLER: ".DATABASE_DBA_HANDLER.", ";
106 $s .= "DATABASE_DIRECTORY: \"".DATABASE_DIRECTORY."\", ";
109 $s .= "DATABASE_DIRECTORY: \"".DATABASE_DIRECTORY."\", ";
110 // $s .= "cvs stuff: , ";
113 $s .= "DATABASE_DIRECTORY: ".DATABASE_DIRECTORY.", ";
116 // hack: suppress error when using sql, so no timeout
117 @$s .= "DATABASE_TIMEOUT: " . DATABASE_TIMEOUT . ", ";
120 function cachestats() {
122 if (! defined('USECACHE') or !USECACHE)
123 return _("no cache used");
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: ?";
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']);
150 function pagestats() {
152 $dbi = $request->getDbh();
153 $s = sprintf(_("%d pages"), $dbi->numPages(true));
154 $s .= ", " . sprintf(_("%d not-empty pages"), $dbi->numPages(false));
156 // $s .= ", " . sprintf(_("earliest page from %s"), $earliestdate);
157 // $s .= ", " . sprintf(_("latest page from %s"), $latestdate);
158 // $s .= ", " . sprintf(_("latest pagerevision from %s"), $latestrevdate);
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() {
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() {
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.
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?
190 //only from logging info possible. = hitstats per time.
191 // total hits per day/month/year
193 // TODO: see WhoIsOnline hit stats, and sql accesslogs
194 function accessstats() {
200 function _stats($hits, $treshold = 10.0) {
204 $max = 0; $min = 9999999999999; $sum = 0;
205 foreach ($hits as $h) {
207 $max = max($h, $max);
208 $min = min($h, $min);
210 $median_i = (int) $n / 2;
212 $median = $hits[$median_i];
214 $median = $hits[$median_i];
216 $mintreshold = $max * $treshold / 100.0; // lower than 10% of the hits
218 $nmin = $hits[0] < $mintreshold ? 1 : 0;
219 while (next($hits) < $mintreshold)
221 $maxtreshold = $max - $mintreshold; // more than 90% of the hits
224 while (prev($hits) > $maxtreshold)
226 return array('n' => $n,
232 'stddev'=> stddev($hits, $sum),
233 'treshold' => $treshold,
235 'mintreshold' => $mintreshold,
237 'maxtreshold' => $maxtreshold);
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() {
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');
257 $stats = $this->_stats($hits, $treshold);
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);
272 function revisionstats() {
273 global $request, $LANG;
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);
281 if (!empty($content))
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()) {
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']++;
299 $stats['latest']['minor']++;
301 // FIXME: This needs much too long to be acceptable.
303 // number of revisions: all (100%) / major (60%) / minor (40%)
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']++;
311 $stats['page']['minor']++;
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();
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'],
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']);
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']);
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;
356 $content = $empty . $latest;
358 // regenerate cache every 30 minutes
359 $cache->save($id, $content, '+1800', $cachedir);
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;
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);
375 if (empty($content)) {
376 $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : '.';
377 //TODO: windows only (no cygwin)
378 $appsize = `du -s $dir | cut -f1`;
380 if (in_array($DBParams['dbtype'], array('SQL', 'ADODB'))) {
381 //TODO: where is the data is actually stored? see phpMyAdmin
383 } elseif ($DBParams['dbtype'] == 'dba') {
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;
395 $content = array('appsize' => $appsize,
396 'pagesize' => $pagesize);
397 // regenerate cache every 30 minutes
398 $cache->save($id, $content, '+1800', $cachedir);
400 $appsize = $content['appsize'];
401 $pagesize = $content['pagesize'];
404 $s = sprintf(_("Application size: %d Kb"), $appsize);
406 $s .= ", " . sprintf(_("Pagedata size: %d Kb", $pagesize));
410 function inlineimages () {
411 return implode(' ', explode('|', INLINE_IMAGES));
413 function wikinameregexp () {
414 return $GLOBALS['WikiNameRegexp'];
416 function allowedprotocols () {
417 return implode(' ', explode('|', ALLOWED_PROTOCOLS));
419 function available_plugins () {
420 $fileset = new FileSet(FindFile('lib/plugin'), '*.php');
421 $list = $fileset->getFiles();
424 return sprintf(_("Total %d plugins: "), count($list))
425 . implode(', ', array_map(create_function('$f',
426 'return substr($f,0,-4);'),
429 function supported_languages () {
430 $available_languages = listAvailableLanguages();
431 natcasesort($available_languages);
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)
442 function supported_themes () {
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)
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);
463 return $this->error(sprintf(_("unknown argument '%s' to SystemInfo"), $arg));
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);
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';")
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"),
508 $table = HTML::table(array('border' => 1,'cellspacing' => 3,
509 'cellpadding' => 3));
510 foreach ($allargs as $arg => $desc) {
515 $table->pushContent(HTML::tr(HTML::td(HTML::strong($desc . ':')),
516 HTML::td(HTML($this->call($arg, $availableargs)))));
521 foreach ($allargs as $arg) {
522 $o = $this->call($arg, $availableargs);
526 $output .= ($o . $args['seperator']);
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);
536 function median($hits) {
540 $median = (int) $n / 2;
541 if (! ($n % 2)) // proper rounding on even length
542 return ($hits[$median] + $hits[$median-1]) * 0.5;
544 return $hits[$median];
547 function rsum($a, $b) {
551 function mean(&$hits, $total = false) {
554 $total = array_reduce($hits, 'rsum');
555 return (float) $total / ($n * 1.0);
557 function gensym($prefix = "_gensym") {
559 while (isset($GLOBALS[$prefix . $i]))
564 function stddev(&$hits, $total = false) {
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);'),
570 unset($GLOBALS['mean']);
571 return (float)sqrt(mean($r, $total) * ($n / (float)($n -1)));
574 // $Log: not supported by cvs2svn $
575 // Revision 1.22 2004/12/26 17:10:44 rurban
576 // just docs or whitespace
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)
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
589 // Revision 1.19 2004/06/19 11:49:42 rurban
590 // dont print db passwords
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
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
604 // Revision 1.16 2004/05/06 20:30:47 rurban
605 // revert and removed some comments
607 // Revision 1.15 2004/05/03 11:40:42 rurban
608 // put listAvailableLanguages() and listAvailableThemes() from SystemInfo and
609 // UserPreferences into Themes.php
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
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)
619 // Revision 1.12 2004/03/14 16:26:21 rurban
622 // Revision 1.11 2004/03/13 19:24:21 rurban
623 // fixed supported_languages() and supported_themes()
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
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, ...)
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.)
637 // Revision 1.7 2003/02/22 20:49:56 dairiki
638 // Fixes for "Call-time pass by reference has been deprecated" errors.
640 // Revision 1.6 2003/02/21 23:01:11 dairiki
641 // Fixes to support new $basepage argument of WikiPlugin::run().
643 // Revision 1.5 2003/01/18 22:08:01 carstenklapp
645 // Reformatting & tabs to spaces;
646 // Added copyleft, getVersion, getDescription, rcs_id.
653 // c-hanging-comment-ender-p: nil
654 // indent-tabs-mode: nil