Version 5.0.13

This commit is contained in:
Neo committed 2025-12-19 19:04:57 -08:00
1 parent b89858b2ff
commit 5efe744c5f
1407 files changed
+69690 -30177

No files matched your search

+275 -13
View File
@@ -12,16 +12,31 @@ namespace IPS\core\Statistics;
/* To prevent PHP errors (extending class does not exist) revealing path */
use DateInterval;
use InvalidArgumentException;
use IPS\Application;
use IPS\DateTime;
use IPS\Db;
use IPS\Helpers\Form;
use IPS\Helpers\Form\DateRange;
use IPS\Helpers\Form\Select;
use IPS\Http\Url;
use IPS\Member;
use IPS\Output;
use IPS\Platform\Bridge;
use IPS\Request;
use IPS\Settings;
use IPS\Theme;
use OutOfRangeException;
use UnderflowException;
use function array_keys;
use function defined;
use function in_array;
use function is_array;
use function is_int;
use function is_numeric;
use function strval;
use function time;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
@@ -37,7 +52,14 @@ abstract class Chart
/**
* @brief Controller
*/
public ?string $controller = NULL;
public ?string $controller = 'NULL';
/**
* Whether this chart can be saved so that extra tabs exist. When this is true, the chart can only be saved to reports
*
* @var bool
*/
public bool $noTabs = false;
/**
* Get Chart
@@ -112,9 +134,9 @@ abstract class Chart
{
if ( !is_array( $data ) )
{
$data = Db::i()->select( '*', 'core_saved_charts', array( "chart_id=? AND chart_member=?", $data, $check->member_id ) )->first();
$data = Db::i()->select( '*', 'core_saved_charts', array( "chart_id=? AND (chart_member=? OR chart_member IS NULL)", $data, $check->member_id ) )->first();
}
else if ( $data['chart_member'] !== $check->member_id )
else if ( $data['chart_member'] !== $check->member_id and $data['chart_member'] !== null and $data['chart_member'] !== 0 )
{
throw new UnderflowException;
}
@@ -123,9 +145,9 @@ abstract class Chart
{
if ( !is_array( $data ) )
{
$data = Db::i()->select( '*','core_saved_charts', array( "chart_id=? AND chart_member=?", $data, Member::loggedIn()->member_id ) )->first();
$data = Db::i()->select( '*', 'core_saved_charts', array( "chart_id=? AND (chart_member=? OR chart_member IS NULL OR chart_member=0)", $data, Member::loggedIn()->member_id ) )->first();
}
else if ( $data['chart_member'] !== Member::loggedIn()->member_id )
else if ( $data['chart_member'] !== Member::loggedIn()->member_id and $data['chart_member'] !== null and $data['chart_member'] !== 0 )
{
throw new UnderflowException;
}
@@ -157,10 +179,14 @@ abstract class Chart
$chart->showFilterTabs = FALSE;
$chart->showIntervals = FALSE;
$chart->showDateRange = FALSE;
$dateRange = static::getChartDateRanges( is_numeric( Request::i()->report_id ) ? (int) Request::i()->report_id : null );
$chart->start = $dateRange['start'];
$chart->end = $dateRange['end'];
return $chart;
}
catch( UnderflowException $e )
catch( UnderflowException )
{
/* Chart doesn't exist, or isn't owned by $member */
throw new OutOfRangeException;
@@ -180,15 +206,34 @@ abstract class Chart
public static function getChartsForMember( Url $url, bool $idsOnly = FALSE, ?Member $member = NULL ): array
{
$member ??= Member::loggedIn();
if ( !$member->member_id )
{
throw new InvalidArgumentException;
}
$return = [];
foreach( Db::i()->select( '*', 'core_saved_charts', array( "chart_member=?", $member->member_id ) ) AS $chart )
if ( Bridge::i()->featureIsEnabled( "community_health_stats" ) and !empty( $url->queryString['communityhealth'] ) )
{
$charts = Bridge::i()->getCommunityHealthCharts();
}
else
{
$return = [];
$reportID = is_numeric( Request::i()->report_id ) ? (int) Request::i()->report_id : 0;
$where = [];
if ( $reportID > 0 )
{
$where[] = array( "(chart_member IS NULL OR chart_member=? OR chart_member=0) AND chart_report_id=?", $member->member_id, $reportID );
}
else
{
$where[] = array( "(chart_member IS NULL OR chart_member=0) AND chart_report_id IS NULL", $member->member_id );
}
$charts = Db::i()->select( '*', 'core_saved_charts', $where );
}
foreach( $charts AS $chart )
{
if ( $idsOnly )
{
@@ -198,13 +243,12 @@ abstract class Chart
{
try
{
$controller = explode( '_', $chart['chart_controller'] );
$return[$chart['chart_id']] = array(
'chart' => static::constructMemberChartFromData( $chart, $url, $member ),
'data' => $chart
);
}
catch( OutOfRangeException )
catch( OutOfRangeException $e )
{
continue;
}
@@ -213,4 +257,222 @@ abstract class Chart
return $return;
}
/**
* Get the saved blocks for the current page.
*
* @return array
*/
public static function getSavedBlocks() : array
{
static $toDisplay = null;
if ( !is_array( $toDisplay ) )
{
$where = [[ "(stat_member IS NULL OR stat_member=?)", Member::loggedIn()->member_id ]];
if ( Request::i()->report_id )
{
$where[] = [ "stat_report_id=?", Request::i()->report_id ];
}
else
{
$where[] = [ "stat_report_id IS NULL" ];
}
$allExtensions = Application::allExtensions( 'core', 'OverviewStatistics', TRUE );
$toDisplay = [];
foreach ( Db::i()->select( "*", "core_saved_overview_stats", $where ) as $savedStat )
{
$key = $savedStat['stat_app'] . "_" . $savedStat['stat_extension'];
if ( isset( $allExtensions[ $key ] ) and in_array( $savedStat['stat_subblock'], $allExtensions[ $key ]->getBlocks() ) )
{
$details = $allExtensions[ $key ]->getBlockDetails( $savedStat['stat_subblock'] );
$details['title'] = $savedStat['stat_title'];
$toDisplay[] = [
"blockKey" => $key,
"subblock" => $savedStat['stat_subblock'],
"saved_stat_id" => $savedStat['id'],
"details" => $details,
'page' => $allExtensions[ $key ]->page,
'extension' => $allExtensions[$key],
];
}
}
}
return $toDisplay;
}
/**
* Get the date range selector that is at the top of the saved reports acp page
* @return string
*/
public static function getBlockDateRangeForm( ?int $reportId=null ) : string
{
$options = array(
'7' => 'last_week',
'30' => 'last_month',
'90' => 'last_three_months',
'180' => 'last_six_months',
'365' => 'last_year',
'0' => 'alltime',
'-1' => 'custom'
);
$defaultRange = '7';
$dateRange = null;
if ( ( is_numeric( Request::i()->predate ) and isset( $options[Request::i()->predate] ) ) or ( is_numeric( @Request::i()->date['start'] ) and is_numeric( @Request::i()->date['end'] ) ) )
{
if ( is_numeric( @Request::i()->date['start'] ) and is_numeric( @Request::i()->date['end'] ) )
{
$dateRange = [
'start' => Request::i()->date['start'],
'end' => Request::i()->date['end']
];
$defaultRange = '-1';
}
else
{
$defaultRange = Request::i()->predate;
}
}
else if ( is_int( $reportId ) )
{
try
{
$currentReport = Db::i()->select( '*', 'core_reports', ['id=?', $reportId] )->first();
$defaultRange = strval($currentReport['report_range_timescale'] ?? '7');
if ( isset( $currentReport['report_range_start'] ) or isset( $currentReport['report_range_end'] ) )
{
$dateRange = [
'start'=> $currentReport['report_range_start'],
'end' => $currentReport['report_range_end'],
];
}
}
catch ( \UnderflowException ) {}
}
$form = new Form( 'posts', 'update' );
$form->add( new Select( 'predate', $defaultRange, FALSE, array( 'options' => $options, 'toggles' => array( '-1' => array( 'dateFilterInputs' ) ) ) ) );
$form->add( new DateRange( 'date', $dateRange, FALSE, array(), NULL, NULL, NULL, 'dateFilterInputs' ) );
$form->class .= " ipsBox ipsPull i-padding_2";
if ( is_int( $reportId ) and $values = $form->values() )
{
$update = [
'report_range_timescale' => (int) $values['predate'],
'report_range_start' => @$values['date']['start']?->getTimestamp() ?: null,
'report_range_end' => @$values['date']['end']?->getTimestamp() ?: null
];
Db::i()->update( 'core_reports', $update, ['id=?', $reportId] );
if ( Request::i()->isAjax() )
{
Output::i()->json( ['message' => 'ok'], 200 );
}
Output::i()->redirect( (string) Url::internal( 'app=core&module=overview&controller=mycharts' ) );
}
return $form->customTemplate( array( Theme::i()->getTemplate( 'stats' ), 'filtersOverviewForm' ) );
}
/**
* @return array
*/
public static function getAppsToFilterBy()
{
static $apps = null;
if ( !is_array( $apps ) )
{
$blocks = static::getSavedBlocks();
$apps = [];
foreach ( $blocks as $block )
{
if ( isset( $block['details']['app'] ) and !isset( $apps[ $block['details']['app'] ] ) )
{
$apps[ $block['details']['app'] ] = Application::load( $block['details']['app'] );
}
}
}
return $apps;
}
/**
* @param int|null $reportId The ID of the report, or leave null to use the default saved charts function
* @return array{start: DateTime|null, end: DateTime|null}
*/
public static function getChartDateRanges( ?int $reportId=null )
{
// It is possible this is included right in the request
if ( isset( Request::i()->predate ) and is_numeric( Request::i()->predate ) and intval( Request::i()->predate ) >= 0 )
{
$days = (int) Request::i()->predate;
if ( $days <= 0 )
{
return [
'start' => null,
'end' => null,
];
}
return [
'end' => DateTime::ts( time() )->setTime( 23, 59, 59 ),
'start' => DateTime::ts( time() )->setTime( 0, 0, 0 )->sub( new DateInterval( "P{$days}D" ) )
];
}
else if ( isset( Request::i()->date['start'] ) and isset( Request::i()->date['end'] ) ) // the way the JS works, the start and end are sent even if it just relies on the predate.
{
$start = is_numeric( Request::i()->date['start'] ) ? DateTime::ts( (int) Request::i()->date['start'] ) : null;
$end = is_numeric( Request::i()->date['end'] ) ? DateTime::ts( (int) Request::i()->date['end'] ) : null;
return [
'start' => $start,
'end' => $end,
];
}
if ( is_null( $reportId ) )
{
if ( isset( Settings::i()->core_saved_charts_range_timescale ) and is_numeric( Settings::i()->core_saved_charts_range_timescale ) and intval( Settings::i()->core_saved_charts_range_timescale ) >= 0 )
{
$days = (int) Settings::i()->core_saved_charts_range_timescale;
if ( $days <= 0 )
{
return [
'start' => null,
'end' => null,
];
}
return [
'end' => DateTime::ts( time() )->setTime( 23, 59, 59 ),
'start' => DateTime::ts( time() )->setTime( 0, 0, 0 )->sub( new DateInterval( "P{$days}D" ) )
];
}
$start = is_numeric( Settings::i()->core_saved_charts_range_start ) ? DateTime::ts( (int) Settings::i()->core_saved_charts_range_start ) : null;
$end = is_numeric( Settings::i()->core_saved_charts_range_end ) ? DateTime::ts( (int) Settings::i()->core_saved_charts_range_end ) : null;
}
else
{
try
{
$row = Db::i()->select( "*", "core_reports", [ "id=?", $reportId ?: 0 ] )->first();
$start = is_int( $row['report_range_start'] ) ? DateTime::ts( $row['report_range_start'] ) : null;
$end = is_int( $row['report_range_end'] ) ? DateTime::ts( $row['end'] ) : null;
}
catch ( UnderflowException $e )
{
$start = null;
$end = null;
}
}
return [
"start" => $start,
"end" => $end
];
}
}