2017-07-13 19:48:57 +00:00
< ? php
2020-09-23 22:22:39 +00:00
/* This Source Code Form is subject to the terms of the Mozilla Public
* License , v . 2.0 . If a copy of the MPL was not distributed with this
* file , You can obtain one at https :// mozilla . org / MPL / 2.0 /. */
2017-07-13 19:48:57 +00:00
register_module ([
" name " => " Statistics " ,
2020-08-09 22:53:29 +00:00
" version " => " 0.4.3 " ,
2017-07-13 19:48:57 +00:00
" author " => " Starbeamrainbowlabs " ,
" description " => " An extensible statistics calculation system. Comes with a range of built-in statistics, but can be extended by other modules too. " ,
" id " => " feature-stats " ,
" code " => function () {
2017-09-16 16:31:30 +00:00
global $settings , $env ;
2017-07-13 19:48:57 +00:00
/**
2017-09-16 16:31:30 +00:00
* @ api { get } ? action = stats Show wiki statistics
* @ apiName Stats
2017-07-14 20:28:28 +00:00
* @ apiGroup Utility
2017-09-16 16:31:30 +00:00
* @ apiPermission Anonymous
2019-08-09 11:44:22 +00:00
* @ apiVersion 0.15 . 0
2017-09-16 16:31:30 +00:00
* @ apiParam { string } format Specify the format the data should be returned in . Supported formats : html ( default ), json .
* @ apiParam { string } stat HTML format only . If specified the page for the stat with this id is sent instead of the list of scalar stats .
2017-07-13 19:48:57 +00:00
*/
/*
2017-07-14 21:13:13 +00:00
* ███████ ████████ █████ ████████ ███████
* ██ ██ ██ ██ ██ ██
* ███████ ██ ███████ ██ ███████
* ██ ██ ██ ██ ██ ██
* ███████ ██ ██ ██ ██ ███████
*/
add_action ( " stats " , function () {
global $settings , $statistic_calculators ;
2017-09-16 16:31:30 +00:00
$allowed_formats = [ " html " , " json " ];
$format = $_GET [ " format " ] ? ? " html " ;
if ( ! in_array ( $format , $allowed_formats )) {
http_response_code ( 400 );
2017-09-18 21:11:32 +00:00
exit ( page_renderer :: render_main ( " Format error - $settings->sitename " , " <p>Error: The format ' $format ' is not currently supported by this action on $settings->sitename . Supported formats: " . implode ( " , " , $allowed_formats ) . " . " ));
2017-09-16 16:31:30 +00:00
}
2017-07-14 21:13:13 +00:00
$stats = stats_load ();
2017-09-16 16:31:30 +00:00
if ( $format == " json " ) {
header ( " content-type: application/json " );
exit ( json_encode ( $stats , JSON_PRETTY_PRINT ));
}
2017-09-16 12:26:12 +00:00
$stat_pages_list = " <a href='?action=stats'>Main</a> | " ;
2017-07-14 21:13:13 +00:00
foreach ( $statistic_calculators as $stat_id => $stat_calculator ) {
2017-09-16 12:26:12 +00:00
if ( $stat_calculator [ " type " ] == " scalar " )
continue ;
$stat_pages_list .= " <a href='?action=stats&stat= " . rawurlencode ( $stat_id ) . " '> { $stat_calculator [ " name " ] } </a> | " ;
2017-07-14 21:13:13 +00:00
}
2017-09-16 12:26:12 +00:00
$stat_pages_list = trim ( $stat_pages_list , " | " );
2017-07-14 21:13:13 +00:00
2017-09-16 12:26:12 +00:00
if ( ! empty ( $_GET [ " stat " ]) && ! empty ( $statistic_calculators [ $_GET [ " stat " ]])) {
$stat_calculator = $statistic_calculators [ $_GET [ " stat " ]];
$content = " <h1> { $stat_calculator [ " name " ] } - Statistics</h1> \n " ;
$content .= " <p> $stat_pages_list </p> \n " ;
2017-09-16 16:31:30 +00:00
switch ( $stat_calculator [ " type " ]) {
case " page-list " :
if ( ! module_exists ( " page-list " )) {
2020-08-09 22:53:29 +00:00
$content .= " <p> $settings->sitename doesn't current have the page listing module installed, so HTML rendering of this statistic is currently unavailable. Try " . hide_email ( $settings -> admindetails_email , " contacting $settings->admindetails_name " ) . " , $settings->sitename 's administrator and asking then to install the <code>page-list</code> module.</p> " ;
2017-09-16 16:31:30 +00:00
break ;
}
2018-04-28 10:06:23 +00:00
$content .= " <p><strong>Count:</strong> " . count ( $stats -> { $_GET [ " stat " ]} -> value ) . " </p> \n " ;
2017-09-16 16:31:30 +00:00
$content .= generate_page_list ( $stats -> { $_GET [ " stat " ]} -> value );
break ;
case " page " :
$content .= $stat_calculator [ " render " ]( $stats -> { $_GET [ " stat " ]});
break ;
}
2017-09-16 12:26:12 +00:00
}
else
{
$content = " <h1>Statistics</h1> \n " ;
2017-09-16 16:31:30 +00:00
$content .= " <p>This page contains a selection of statistics about $settings->sitename 's content. They are updated automatically about every " . trim ( str_replace ([ " ago " , " 1 " ], [ " " ], human_time ( $settings -> stats_update_interval ))) . " , although $settings->sitename 's local friendly moderators may update them earlier (you can see their names at the bottom of every page).</p> \n " ;
2017-09-16 12:26:12 +00:00
$content .= " <p> $stat_pages_list </p> \n " ;
$content .= " <table class='stats-table'> \n " ;
$content .= " \t <tr><th>Statistic</th><th>Value</th></tr> \n \n " ;
foreach ( $statistic_calculators as $stat_id => $stat_calculator ) {
if ( $stat_calculator [ " type " ] !== " scalar " )
continue ;
$content .= " \t <tr><td> { $stat_calculator [ " name " ] } </td><td> { $stats -> $stat_id -> value } </td></tr> \n " ;
}
$content .= " </table> \n " ;
2017-07-14 21:13:13 +00:00
}
exit ( page_renderer :: render_main ( " Statistics - $settings->sitename " , $content ));
});
2017-09-16 16:31:30 +00:00
/**
* @ api { get | post } ? action = stats - update Recalculate the wiki ' s statistics
* @ apiName UpdateStats
* @ apiGroup Utility
* @ apiPermission Administrator
2019-08-09 11:44:22 +00:00
* @ apiVersion 0.15 . 0
2017-09-16 16:31:30 +00:00
* @ apiParam { string } secret POST only , optional . If you 're not logged in, you can specify the wiki' s sekret instead ( find it in peppermint . json ) using this parameter .
* @ apiParam { bool } force Whether the statistics should be recalculated anyway - even if they have already recently been recalculated . Default : no . Supported values : yes , no .
*/
2017-07-14 20:28:28 +00:00
2017-07-14 21:13:13 +00:00
/*
* ███████ ████████ █████ ████████ ███████
* ██ ██ ██ ██ ██ ██
* ███████ ██ ███████ ██ ███████
* ██ ██ ██ ██ ██ ██
* ███████ ██ ██ ██ ██ ███████
*
* ██ ██ ██████ ██████ █████ ████████ ███████
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ██ ██ ██████ ██ ██ ███████ ██ █████
* ██ ██ ██ ██ ██ ██ ██ ██ ██
* ██████ ██ ██████ ██ ██ ██ ███████
2017-07-13 19:48:57 +00:00
*/
add_action ( " stats-update " , function () {
2017-07-13 21:12:12 +00:00
global $env , $paths , $settings ;
2017-07-14 19:35:55 +00:00
if ( ! $env -> is_admin &&
(
empty ( $_POST [ " secret " ]) ||
$_POST [ " secret " ] !== $settings -> secret
)
)
2017-07-13 21:12:12 +00:00
exit ( page_renderer :: render_main ( " Error - Recalculating Statistics - $settings->sitename " , " <p>You need to be logged in as a moderator or better to get $settings->sitename to recalculate it's statistics. If you're logged in, try <a href='?action=logout'>logging out</a> and logging in again as a moderator. If you aren't logged in, try <a href='?action=login&returnto=%3Faction%3Dstats-update'>logging in</a>.</p> " ));
2017-07-13 19:48:57 +00:00
2017-07-14 20:22:37 +00:00
// Delete the old stats cache
2020-11-20 21:13:31 +00:00
if ( file_exists ( $paths -> statsindex ))
unlink ( $paths -> statsindex );
2017-07-14 20:22:37 +00:00
2017-09-16 16:31:30 +00:00
update_statistics ( true , ( $_GET [ " force " ] ? ? " no " ) == " yes " );
2017-07-13 19:48:57 +00:00
header ( " content-type: application/json " );
echo ( file_get_contents ( $paths -> statsindex ) . " \n " );
});
2019-08-24 18:56:14 +00:00
add_help_section ( " 150-statistics " , " Statistics " , " <p> $settings->sitename records some statistics about itself, including the number of pages, the longest pages, the most wanted pages, the most linked-to pages, and more. They are updated roughly every " . human_time ( $settings -> stats_update_interval ) . " , though moderators may occasionally update them sooner.</p>
< p > You can see these statistics < a href = '?action=stats' > here </ a >.</ p > " );
2017-07-13 19:48:57 +00:00
//////////////////////////
/// Built-in Statisics ///
//////////////////////////
2018-04-26 22:20:52 +00:00
2017-07-13 19:48:57 +00:00
2018-04-26 22:20:52 +00:00
statistic_add ([
" id " => " user_count " ,
" name " => " Users " ,
" type " => " scalar " ,
" update " => function ( $old_stats ) {
global $settings ;
$result = new stdClass (); // completed, value, state
$result -> completed = true ;
$result -> value = count ( get_object_vars ( $settings -> users ));
return $result ;
}
]);
2017-07-13 19:48:57 +00:00
statistic_add ([
" id " => " longest-pages " ,
" name " => " Longest Pages " ,
2018-04-28 10:13:58 +00:00
" type " => " page-list " ,
2017-07-13 19:48:57 +00:00
" update " => function ( $old_stats ) {
global $pageindex ;
$result = new stdClass (); // completed, value, state
$pages = [];
foreach ( $pageindex as $pagename => $pagedata ) {
$pages [ $pagename ] = $pagedata -> size ;
}
arsort ( $pages );
2018-04-28 10:13:58 +00:00
$result -> value = array_keys ( $pages );
2017-07-13 19:48:57 +00:00
$result -> completed = true ;
return $result ;
}
]);
statistic_add ([
" id " => " page_count " ,
" name " => " Page Count " ,
2017-09-16 12:26:12 +00:00
" type " => " scalar " ,
2017-07-13 19:48:57 +00:00
" update " => function ( $old_stats ) {
global $pageindex ;
$result = new stdClass (); // completed, value, state
$result -> completed = true ;
$result -> value = count ( get_object_vars ( $pageindex ));
return $result ;
}
]);
statistic_add ([
" id " => " file_count " ,
" name " => " File Count " ,
2017-09-16 12:26:12 +00:00
" type " => " scalar " ,
2017-07-13 19:48:57 +00:00
" update " => function ( $old_stats ) {
global $pageindex ;
$result = new stdClass (); // completed, value, state
$result -> completed = true ;
$result -> value = 0 ;
foreach ( $pageindex as $pagename => $pagedata ) {
if ( ! empty ( $pagedata -> uploadedfile ) && $pagedata -> uploadedfile )
$result -> value ++ ;
}
return $result ;
}
]);
2018-04-26 22:20:52 +00:00
statistic_add ([
" id " => " redirect_count " ,
" name " => " Redirect Pages " ,
" type " => " scalar " ,
" update " => function ( $old_stats ) {
global $pageindex ;
$result = new stdClass (); // completed, value, state
$result -> completed = true ;
$result -> value = 0 ;
foreach ( $pageindex as $pagename => $pagedata ) {
if ( ! empty ( $pagedata -> redirect ) && $pagedata -> redirect )
$result -> value ++ ;
}
return $result ;
}
]);
2017-08-14 17:48:51 +00:00
2020-04-21 23:58:57 +00:00
// Perform an automatic recalculation of the statistics if needed, but only if we're not on the CLI
if ( $env -> action !== " stats-update " && ! is_cli ())
2017-09-16 16:31:30 +00:00
update_statistics ( false );
2020-04-21 23:58:57 +00:00
2020-04-22 00:17:05 +00:00
/*
* ██████ ██ ██
* ██ ██ ██
* ██ ██ ██
* ██ ██ ██
* ██████ ███████ ██
*/
2020-04-21 23:58:57 +00:00
if ( module_exists ( " feature-cli " )) {
cli_register ( " stats " , " Interact with and update the wiki statistics " , function ( array $args ) : int {
global $settings , $env ;
if ( count ( $args ) < 1 ) {
echo ( " stats: interact with an manipulate the wiki statistics
Usage :
stats { subcommand }
Subcommands :
recalculate Recalculates the statistics
2020-04-22 00:17:05 +00:00
show Shows the current statistics
2020-04-21 23:58:57 +00:00
" );
return 0 ;
}
switch ( $args [ 0 ]) {
case " recalculate " :
echo ( " Updating statistics - " );
$start_time = microtime ( true );
update_statistics ( true , true );
echo ( " done in " . round (( microtime ( true ) - $start_time ) * 1000 , 2 ) . " ms \n " );
echo ( " Recalculated { $env -> perfdata -> stats_recalcuated } statistics in { $env -> perfdata -> stats_calctime } ms (not including serialisation / saving to disk) \n " );
break ;
2020-04-22 00:17:05 +00:00
case " show " :
$stats = stats_load ();
foreach ( $stats as $name => $stat ) {
$lastupdated = render_timestamp ( $stat -> lastupdated , true , false );
if ( is_object ( $stat -> value )) {
echo ( " *** $stat->name *** (last updated $lastupdated ) \n " );
$i = 0 ;
foreach ( $stat -> value as $key => $value ) {
if ( $i >= 25 ) break ;
echo ( " $key : $value\n " );
$i ++ ;
}
}
else if ( is_array ( $stat -> value )) {
// Display array differently, and truncate to 25 entries
echo ( " *** $stat->name *** (last updated $lastupdated ) \n " );
echo ( implode ( " \n " , array_slice ( $stat -> value , 0 , 25 )));
echo ( " \n " );
}
else
echo ( " $stat->name : " . var_export ( $stat -> value , true ) . " (last updated $lastupdated ) \n " );
echo ( " \n " );
}
break ;
2020-04-21 23:58:57 +00:00
}
return 0 ;
});
}
2017-07-13 19:48:57 +00:00
}
]);
2017-09-16 16:31:30 +00:00
/**
* Updates the wiki ' s statistics .
* @ package feature - stats
2020-01-05 20:59:21 +00:00
* @ param bool $update_all Whether all the statistics should be checked and recalculated , or just as many as we have time for according to the settings .
* @ param bool $force Whether we should recalculate statistics that don ' t currently require recalculating anyway .
2017-09-16 16:31:30 +00:00
*/
function update_statistics ( $update_all = false , $force = false )
2017-07-13 19:48:57 +00:00
{
2020-04-21 23:58:57 +00:00
global $settings , $env , $paths , $statistic_calculators ;
2019-12-08 20:45:20 +00:00
2020-08-08 21:01:12 +00:00
// If the firstrun wizard isn't complete, then there's no point in updating the statistics index
if ( isset ( $settings -> firstrun_complete ) && $settings -> firstrun_complete == false )
return ;
2020-11-20 21:13:31 +00:00
$stats_mtime = file_exists ( $paths -> statsindex ) ? filemtime ( $paths -> statsindex ) : 0 ;
2017-07-13 19:48:57 +00:00
2017-09-16 16:31:30 +00:00
// Clear the existing statistics if we are asked to recalculate them all
if ( $force )
stats_save ( new stdClass ());
2019-12-08 20:45:20 +00:00
// If the stats index exists and has been modified recently, then don't
// even bother to load it
// This is an important optimisation, because json_decode is *slow*
else if ( file_exists ( $paths -> statsindex ) && time () - $stats_mtime < $settings -> stats_update_interval )
return ;
2017-09-16 16:31:30 +00:00
2017-07-13 19:48:57 +00:00
$stats = stats_load ();
$start_time = microtime ( true );
2019-12-08 20:45:20 +00:00
$ran_out_of_time = false ;
2017-07-13 19:48:57 +00:00
$stats_updated = 0 ;
foreach ( $statistic_calculators as $stat_id => $stat_calculator )
{
// If statistic doesn't exist or it's out of date then we should recalculate it.
// Otherwise, leave it and continue on to the next stat.
if ( ! empty ( $stats -> $stat_id ) && $start_time - $stats -> $stat_id -> lastupdated < $settings -> stats_update_interval )
continue ;
$mod_start_time = microtime ( true );
// Run the statistic calculator, passing in the existing stats data
$calculated = $stat_calculator [ " update " ]( ! empty ( $stats -> $stat_id ) ? $stats -> $stat_id : new stdClass ());
$new_stat_data = new stdClass ();
$new_stat_data -> id = $stat_id ;
$new_stat_data -> name = $stat_calculator [ " name " ];
$new_stat_data -> lastupdated = $calculated -> completed ? $mod_start_time : $stats -> $stat_id -> lastupdated ;
$new_stat_data -> value = $calculated -> value ;
if ( ! empty ( $calculated -> state ))
$new_stat_data -> state = $calculated -> state ;
// Save the new statistics
$stats -> $stat_id = $new_stat_data ;
$stats_updated ++ ;
2019-12-08 20:45:20 +00:00
// Check to make sure we haven't run out of time to update the statistics this session
if ( ! $update_all && microtime ( true ) - $start_time >= $settings -> stats_update_processingtime ) {
$ran_out_of_time = true ;
2017-07-13 19:48:57 +00:00
break ;
2019-12-08 20:45:20 +00:00
}
2017-07-13 19:48:57 +00:00
}
2020-04-21 23:58:57 +00:00
$env -> perfdata -> stats_recalcuated = $stats_updated ;
$env -> perfdata -> stats_calctime = round (( microtime ( true ) - $start_time ) * 1000 , 3 );
if ( ! is_cli ()) {
header ( " x-stats-recalculated: { $env -> perfdata -> stats_recalcuated } " );
//round((microtime(true) - $pageindex_read_start)*1000, 3)
header ( " x-stats-calctime: { $env -> perfdata -> stats_calctime } ms " );
}
2017-07-13 19:48:57 +00:00
stats_save ( $stats );
2019-12-08 20:45:20 +00:00
// If we ran out of time, reset the mtime for performance reasons (see the
// beginning of this function)
if ( $ran_out_of_time )
touch ( $paths -> statsindex , $stats_mtime );
2017-07-13 19:48:57 +00:00
}
/**
* Loads and returns the statistics cache file .
2017-09-15 22:06:10 +00:00
* @ package feature - stats
* @ return object The loaded & decoded statistics .
2017-07-13 19:48:57 +00:00
*/
function stats_load ()
{
global $paths ;
static $stats = null ;
if ( $stats == null )
$stats = file_exists ( $paths -> statsindex ) ? json_decode ( file_get_contents ( $paths -> statsindex )) : new stdClass ();
return $stats ;
}
/**
* Saves the statistics back to disk .
2017-09-15 22:06:10 +00:00
* @ package feature - stats
2017-07-13 19:48:57 +00:00
* @ param object The statistics cache to save .
* @ return bool Whether saving succeeded or not .
*/
function stats_save ( $stats )
{
global $paths ;
return file_put_contents ( $paths -> statsindex , json_encode ( $stats , JSON_PRETTY_PRINT ) . " \n " );
}