Version 5.0.0 beta 1
This commit is contained in:
1 parent
25ddeb65d6
commit
15c7beabc5
6736 files changed
+627902
-497943
No files matched your search
@@ -11,9 +11,21 @@
|
||||
namespace IPS\Helpers\Chart;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use DateTimeZone;
|
||||
use Exception;
|
||||
use IPS\DateTime;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Member;
|
||||
use IPS\Request;
|
||||
use function count;
|
||||
use function defined;
|
||||
use function in_array;
|
||||
use function substr;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -22,36 +34,36 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
* This is similar to \IPS\Helpers\Chart\Database but with results supplied manually in user-land code.
|
||||
* Useful when you need to merge results from multiple sources.
|
||||
*/
|
||||
class _Callback extends \IPS\Helpers\Chart\Dynamic
|
||||
class Callback extends Dynamic
|
||||
{
|
||||
/**
|
||||
* @brief Callback to retrieve results
|
||||
*/
|
||||
public $callback;
|
||||
public mixed $callback = NULL;
|
||||
|
||||
/**
|
||||
* @brief Custom form
|
||||
*/
|
||||
public $customFiltersForm = FALSE;
|
||||
public bool|array $customFiltersForm = FALSE;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \IPS\Http\Url $url The URL the chart will be displayed on
|
||||
* @param callable $callback Callback to fetch results
|
||||
* @param string $title Title
|
||||
* @param array $options Options
|
||||
* @param string $defaultType The default chart type
|
||||
* @param string $defaultTimescale The default timescale to use
|
||||
* @param array $defaultTimes The default start/end times to use
|
||||
* @param string $identifier If there will be more than one chart per page, provide a unique identifier
|
||||
* @param \IPS\DateTime|NULL $minimumDate The earliest available date for this chart
|
||||
* @see <a href='https://google-developers.appspot.com/chart/interactive/docs/gallery'>Charts Gallery - Google Charts - Google Developers</a>
|
||||
* @param Url $url The URL the chart will be displayed on
|
||||
* @param callable|null $callback Callback to fetch results
|
||||
* @param string $title Title
|
||||
* @param array $options Options
|
||||
* @param string $defaultType The default chart type
|
||||
* @param string $defaultTimescale The default timescale to use
|
||||
* @param array $defaultTimes The default start/end times to use
|
||||
* @param string $identifier If there will be more than one chart per page, provide a unique identifier
|
||||
* @param DateTime|null $minimumDate The earliest available date for this chart
|
||||
* @return void
|
||||
*@see <a href='https://google-developers.appspot.com/chart/interactive/docs/gallery'>Charts Gallery - Google Charts - Google Developers</a>
|
||||
*/
|
||||
public function __construct( \IPS\Http\Url $url, $callback=NULL, $title='', $options=array(), $defaultType='AreaChart', $defaultTimescale='monthly', $defaultTimes=array( 'start' => 0, 'end' => 0 ), $identifier='', $minimumDate=NULL )
|
||||
public function __construct( Url $url, callable $callback=NULL, string $title='', array $options=array(), string $defaultType='AreaChart', string $defaultTimescale='monthly', array $defaultTimes=array( 'start' => 0, 'end' => 0 ), string $identifier='', DateTime $minimumDate=NULL )
|
||||
{
|
||||
$this->identifier = \substr( md5( (string) $url ), 0, 6 ) . $identifier . ( \IPS\Request::i()->chartId ?: '_default' );
|
||||
$this->identifier = substr( md5( (string) $url ), 0, 6 ) . $identifier . ( Request::i()->chartId ?: '_default' );
|
||||
$this->callback = $callback;
|
||||
|
||||
parent::__construct( $url, $title, $options, $defaultType, $defaultTimescale, $defaultTimes, $identifier, $minimumDate );
|
||||
@@ -60,23 +72,23 @@ class _Callback extends \IPS\Helpers\Chart\Dynamic
|
||||
/**
|
||||
* Add Series
|
||||
*
|
||||
* @param string $name Name
|
||||
* @param string $type Type of value
|
||||
* @param string $name Name
|
||||
* @param string $type Type of value
|
||||
* @li string
|
||||
* @li number
|
||||
* @li boolean
|
||||
* @li date
|
||||
* @li datetime
|
||||
* @li timeofday
|
||||
* @param bool $filterable If TRUE, will show as a filter option to be toggled on/off
|
||||
* @param bool $filterable If TRUE, will show as a filter option to be toggled on/off
|
||||
* @return void
|
||||
*/
|
||||
public function addSeries( $name, $type, $filterable=TRUE )
|
||||
public function addSeries( string $name, string $type, bool $filterable=TRUE ) : void
|
||||
{
|
||||
\IPS\Member::loggedIn()->language()->parseOutputForDisplay( $name );
|
||||
Member::loggedIn()->language()->parseOutputForDisplay( $name );
|
||||
$filterKey = $name;
|
||||
|
||||
if ( !$filterable or !isset( \IPS\Request::i()->filters[ $this->identifier ] ) or \in_array( $filterKey, \IPS\Request::i()->filters[ $this->identifier ] ) )
|
||||
if ( !$filterable or !isset( Request::i()->filters[ $this->identifier ] ) or in_array( $filterKey, Request::i()->filters[ $this->identifier ] ) )
|
||||
{
|
||||
if ( $this->type !== 'PieChart' and $this->type !== 'GeoChart' )
|
||||
{
|
||||
@@ -89,7 +101,7 @@ class _Callback extends \IPS\Helpers\Chart\Dynamic
|
||||
{
|
||||
$this->currentFilters[] = $filterKey;
|
||||
|
||||
if ( isset( \IPS\Request::i()->filters[ $this->identifier ] ) )
|
||||
if ( isset( Request::i()->filters[ $this->identifier ] ) )
|
||||
{
|
||||
$this->url = $this->url->setQueryString( 'filters', array( $this->identifier => $this->currentFilters ) );
|
||||
}
|
||||
@@ -101,14 +113,13 @@ class _Callback extends \IPS\Helpers\Chart\Dynamic
|
||||
$this->availableFilters[ $filterKey ] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compile Data for Output
|
||||
*
|
||||
* @param array $data The data
|
||||
* @return void
|
||||
* @return void
|
||||
*/
|
||||
public function compileForOutput()
|
||||
public function compileForOutput() : void
|
||||
{
|
||||
/* Init data */
|
||||
$data = $this->initData();
|
||||
@@ -122,7 +133,7 @@ class _Callback extends \IPS\Helpers\Chart\Dynamic
|
||||
{
|
||||
foreach ( $rows as $k => $v )
|
||||
{
|
||||
if( \count( $this->availableFilters ) and !\in_array( $k, $this->currentFilters ) )
|
||||
if( count( $this->availableFilters ) and !in_array( $k, $this->currentFilters ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -153,18 +164,18 @@ class _Callback extends \IPS\Helpers\Chart\Dynamic
|
||||
$max = NULL;
|
||||
foreach ( $data as $time => $d )
|
||||
{
|
||||
$datetime = new \IPS\DateTime;
|
||||
$datetime = new DateTime;
|
||||
|
||||
if ( \IPS\Member::loggedIn()->timezone )
|
||||
if ( Member::loggedIn()->timezone )
|
||||
{
|
||||
try
|
||||
{
|
||||
$datetime->setTimezone( new \DateTimeZone( \IPS\Member::loggedIn()->timezone ) );
|
||||
$datetime->setTimezone( new DateTimeZone( Member::loggedIn()->timezone ) );
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
catch ( Exception $e )
|
||||
{
|
||||
\IPS\Member::loggedIn()->timezone = null;
|
||||
\IPS\Member::loggedIn()->save();
|
||||
Member::loggedIn()->timezone = null;
|
||||
Member::loggedIn()->save();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +192,7 @@ class _Callback extends \IPS\Helpers\Chart\Dynamic
|
||||
switch ( $this->timescale )
|
||||
{
|
||||
case 'none':
|
||||
$datetime = \IPS\DateTime::ts( $time );
|
||||
$datetime = DateTime::ts( $time );
|
||||
break;
|
||||
|
||||
case 'daily':
|
||||
@@ -209,14 +220,14 @@ class _Callback extends \IPS\Helpers\Chart\Dynamic
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->addRow( array_merge( array( $datetime ), array_fill( 0, \count( $this->series ), 0 ) ) );
|
||||
$this->addRow( array_merge( array( $datetime ), array_fill( 0, count( $this->series ), 0 ) ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( \count($d) < \count($this->series) )
|
||||
if( count($d) < count($this->series) )
|
||||
{
|
||||
$this->addRow( array_merge( array( $datetime ), $d, array_fill( 0, \count($this->series) - \count($d), 0 ) ) );
|
||||
$this->addRow( array_merge( array( $datetime ), $d, array_fill( 0, count($this->series) - count($d), 0 ) ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -225,7 +236,7 @@ class _Callback extends \IPS\Helpers\Chart\Dynamic
|
||||
}
|
||||
}
|
||||
|
||||
if ( \count( $data ) === 1 )
|
||||
if ( count( $data ) === 1 )
|
||||
{
|
||||
$this->options['domainAxis']['type'] = 'category';
|
||||
}
|
||||
@@ -237,12 +248,13 @@ class _Callback extends \IPS\Helpers\Chart\Dynamic
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOutput()
|
||||
public function getOutput(): string
|
||||
{
|
||||
$this->compileForOutput();
|
||||
if ( isset( \IPS\Request::i()->download ) )
|
||||
if ( isset( Request::i()->download ) )
|
||||
{
|
||||
return $this->download();
|
||||
$this->download();
|
||||
return '';
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -11,9 +11,26 @@
|
||||
namespace IPS\Helpers;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use InvalidArgumentException;
|
||||
use IPS\DateTime;
|
||||
use IPS\Output;
|
||||
use IPS\Request;
|
||||
use IPS\Theme;
|
||||
use LengthException;
|
||||
use LogicException;
|
||||
use function count;
|
||||
use function defined;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
use function is_bool;
|
||||
use function is_null;
|
||||
use function is_numeric;
|
||||
use function is_string;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -38,30 +55,30 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
) );
|
||||
* @endcode
|
||||
*/
|
||||
class _Chart
|
||||
class Chart
|
||||
{
|
||||
/**
|
||||
* @brief Headers
|
||||
* @see addHeader();
|
||||
*/
|
||||
public $headers = array();
|
||||
public array $headers = array();
|
||||
|
||||
/**
|
||||
* @brief Rows
|
||||
* @see addHeader();
|
||||
*/
|
||||
public $rows = array();
|
||||
public array $rows = array();
|
||||
|
||||
/**
|
||||
* @brief Google Charts will assume numbers can be negative, which can produce graphs showing negative data points if no data is provided. The default baheviour is to set the minimum value to 0. Change this property if the chart should be able to show negative number values.
|
||||
*/
|
||||
public $numbersCanBeNegative = FALSE;
|
||||
public bool $numbersCanBeNegative = FALSE;
|
||||
|
||||
/**
|
||||
* Add Header
|
||||
*
|
||||
* @param string $label Label
|
||||
* @param string $type Type of value
|
||||
* @param string $label Label
|
||||
* @param string $type Type of value
|
||||
* @li string
|
||||
* @li number
|
||||
* @li boolean
|
||||
@@ -70,7 +87,7 @@ class _Chart
|
||||
* @li timeofday
|
||||
* @return void
|
||||
*/
|
||||
public function addHeader( $label, $type )
|
||||
public function addHeader( string $label, string $type ) : void
|
||||
{
|
||||
$this->headers[] = array( 'label' => $label, 'type' => $type );
|
||||
}
|
||||
@@ -78,55 +95,55 @@ class _Chart
|
||||
/**
|
||||
* Add Row
|
||||
*
|
||||
* @param array $values Values, in the order that headers were added
|
||||
* @param array $values Values, in the order that headers were added
|
||||
* @return void
|
||||
* @throws \LogicException
|
||||
* @throws LogicException
|
||||
*/
|
||||
public function addRow( $values )
|
||||
public function addRow( array $values ) : void
|
||||
{
|
||||
if ( \count( $values ) !== \count( $this->headers ) )
|
||||
if ( count( $values ) !== count( $this->headers ) )
|
||||
{
|
||||
throw new \LengthException('COLUMN_COUNT_MISMATCH');
|
||||
throw new LengthException('COLUMN_COUNT_MISMATCH');
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
$values = array_values( $values );
|
||||
foreach ( $this->headers as $data )
|
||||
{
|
||||
$value = \is_array( $values[ $i ] ) ? $values[ $i ]['value'] : $values[ $i ];
|
||||
$value = is_array( $values[ $i ] ) ? $values[ $i ]['value'] : $values[ $i ];
|
||||
|
||||
switch ( $data['type'] )
|
||||
{
|
||||
case 'string':
|
||||
if ( !\is_string( $value ) )
|
||||
if ( !is_string( $value ) )
|
||||
{
|
||||
throw new \InvalidArgumentException( "VALUE_{$i}_NOT_STRING" );
|
||||
throw new InvalidArgumentException( "VALUE_{$i}_NOT_STRING" );
|
||||
}
|
||||
break;
|
||||
|
||||
case 'number':
|
||||
if ( !\is_numeric( $value ) and !\is_null( $value ) )
|
||||
if ( !is_numeric( $value ) and !is_null( $value ) )
|
||||
{
|
||||
throw new \InvalidArgumentException( "VALUE_{$i}_NOT_NUMBER" );
|
||||
throw new InvalidArgumentException( "VALUE_{$i}_NOT_NUMBER" );
|
||||
}
|
||||
break;
|
||||
|
||||
case 'bool':
|
||||
if ( !\is_bool( $value ) )
|
||||
if ( !is_bool( $value ) )
|
||||
{
|
||||
throw new \InvalidArgumentException( "VALUE_{$i}_NOT_BOOL" );
|
||||
throw new InvalidArgumentException( "VALUE_{$i}_NOT_BOOL" );
|
||||
}
|
||||
break;
|
||||
|
||||
case 'date':
|
||||
case 'datetime':
|
||||
case 'timeofday':
|
||||
if ( !( $value instanceof \IPS\DateTime ) )
|
||||
if ( !( $value instanceof DateTime ) )
|
||||
{
|
||||
throw new \InvalidArgumentException( "VALUE_{$i}_NOT_DATETIME" );
|
||||
throw new InvalidArgumentException( "VALUE_{$i}_NOT_DATETIME" );
|
||||
}
|
||||
|
||||
if ( \is_array( $values[ $i ] ) )
|
||||
if ( is_array( $values[ $i ] ) )
|
||||
{
|
||||
$values[ $i ]['value'] = $value->rfc3339();
|
||||
}
|
||||
@@ -145,25 +162,25 @@ class _Chart
|
||||
/**
|
||||
* Render
|
||||
*
|
||||
* @param string $type Type
|
||||
* @param array $options Options
|
||||
* @param string $format Value for number formatter
|
||||
* @see <a href='https://google-developers.appspot.com/chart/interactive/docs/gallery'>Charts Gallery - Google Charts - Google Developers</a>
|
||||
* @param string $type Type
|
||||
* @param array $options Options
|
||||
* @param string|null $format Value for number formatter
|
||||
* @return string
|
||||
*@see <a href='https://google-developers.appspot.com/chart/interactive/docs/gallery'>Charts Gallery - Google Charts - Google Developers</a>
|
||||
*/
|
||||
public function render( $type, $options=array(), $format=NULL )
|
||||
public function render( string $type, array $options=array(), string $format=NULL ): string
|
||||
{
|
||||
if ( !\IPS\Request::i()->isAjax() )
|
||||
if ( !Request::i()->isAjax() )
|
||||
{
|
||||
\IPS\Output::i()->jsFiles[] = 'https://www.gstatic.com/charts/loader.js';
|
||||
\IPS\Output::i()->headJs .= "google.charts.load( '47', { 'packages':['corechart'] } );";
|
||||
Output::i()->jsFiles[] = 'https://www.gstatic.com/charts/loader.js';
|
||||
Output::i()->headJs .= "google.charts.load( '47', { 'packages':['corechart'] } );";
|
||||
}
|
||||
|
||||
if ( !$this->numbersCanBeNegative and \in_array( $type, array( 'LineChart', 'ColumnChart' ) ) and !isset( $options['vAxis']['viewWindow']['min'] ) )
|
||||
if ( !$this->numbersCanBeNegative and in_array( $type, array( 'LineChart', 'ColumnChart' ) ) and !isset( $options['vAxis']['viewWindow']['min'] ) )
|
||||
{
|
||||
$options['vAxis']['viewWindow']['min'] = 0;
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->chart( $this, $type, json_encode( $options ), $format );
|
||||
return Theme::i()->getTemplate( 'global', 'core', 'global' )->chart( $this, $type, json_encode( $options ), $format );
|
||||
}
|
||||
}
|
||||
+145
-131
@@ -11,99 +11,114 @@
|
||||
namespace IPS\Helpers\Chart;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use DateTimeZone;
|
||||
use Exception;
|
||||
use IPS\DateTime;
|
||||
use IPS\Db;
|
||||
use IPS\Helpers\Table\Db as TableDb;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Member;
|
||||
use IPS\Output;
|
||||
use IPS\Request;
|
||||
use function count;
|
||||
use function defined;
|
||||
use function header;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
use function mb_strlen;
|
||||
use function mb_substr;
|
||||
use function md5;
|
||||
use function substr;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic Database Chart Helper
|
||||
*/
|
||||
class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
class Database extends Dynamic
|
||||
{
|
||||
/**
|
||||
* @brief Database Table
|
||||
*/
|
||||
protected $table;
|
||||
protected ?string $table = null;
|
||||
|
||||
/**
|
||||
* @brief Database column that contains date
|
||||
*/
|
||||
protected $dateField;
|
||||
protected ?string $dateField = null;
|
||||
|
||||
/**
|
||||
* @brief Where clauses
|
||||
*/
|
||||
public $where = array();
|
||||
public array $where = array();
|
||||
|
||||
/**
|
||||
* @brief Query joins
|
||||
*/
|
||||
public $joins = array();
|
||||
public array $joins = array();
|
||||
|
||||
/**
|
||||
* @brief Extra column to group by (useful for multi-data line charts)
|
||||
*/
|
||||
public $groupBy = '';
|
||||
public string $groupBy = '';
|
||||
|
||||
/**
|
||||
* @brief Group by keys for the series
|
||||
*/
|
||||
protected $groupByKeys = array();
|
||||
protected array $groupByKeys = array();
|
||||
|
||||
/**
|
||||
* @brief Table Columns
|
||||
*/
|
||||
public $tableInclude = array();
|
||||
public array $tableInclude = array();
|
||||
|
||||
/**
|
||||
* @brief Table Column Formatters
|
||||
*/
|
||||
public $tableParsers = array();
|
||||
|
||||
/**
|
||||
* @brief Table Lang Prefix
|
||||
*/
|
||||
public $tableLangPrefix = NULL;
|
||||
public array $tableParsers = array();
|
||||
|
||||
/**
|
||||
* @brief Along with the other series, show one of the sum of all
|
||||
*/
|
||||
public $allRecordsSeries = FALSE;
|
||||
public bool $allRecordsSeries = FALSE;
|
||||
|
||||
/**
|
||||
* @brief The lang string used to label the 'all records' series
|
||||
*/
|
||||
public $allRecordsLang = 'database_chart_all_series';
|
||||
public string $allRecordsLang = 'database_chart_all_series';
|
||||
|
||||
/**
|
||||
* @brief Custom form
|
||||
*/
|
||||
public $customFiltersForm = FALSE;
|
||||
public bool|array $customFiltersForm = FALSE;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \IPS\Http\Url $url The URL the chart will be displayed on
|
||||
* @param string $table Database Table
|
||||
* @param string $dateField Database column that contains date
|
||||
* @param string $title Title
|
||||
* @param array $options Options
|
||||
* @param string $defaultType The default chart type
|
||||
* @param string $defaultTimescale The default timescale to use
|
||||
* @param array $defaultTimes The default start/end times to use
|
||||
* @param array $tableInclude Table columns to include in results
|
||||
* @param string $identifier If there will be more than one chart per page, provide a unique identifier
|
||||
* @param \IPS\DateTime|NULL $minimumDate The earliest available date for this chart
|
||||
* @see <a href='https://google-developers.appspot.com/chart/interactive/docs/gallery'>Charts Gallery - Google Charts - Google Developers</a>
|
||||
* @param Url $url The URL the chart will be displayed on
|
||||
* @param string $table Database Table
|
||||
* @param string $dateField Database column that contains date
|
||||
* @param string $title Title
|
||||
* @param array $options Options
|
||||
* @param string $defaultType The default chart type
|
||||
* @param string $defaultTimescale The default timescale to use
|
||||
* @param array $defaultTimes The default start/end times to use
|
||||
* @param array $tableInclude Table columns to include in results
|
||||
* @param string $identifier If there will be more than one chart per page, provide a unique identifier
|
||||
* @param DateTime|null $minimumDate The earliest available date for this chart
|
||||
* @return void
|
||||
*@see <a href='https://google-developers.appspot.com/chart/interactive/docs/gallery'>Charts Gallery - Google Charts - Google Developers</a>
|
||||
*/
|
||||
public function __construct( \IPS\Http\Url $url, $table, $dateField, $title='', $options=array(), $defaultType='AreaChart', $defaultTimescale='monthly', $defaultTimes=array( 'start' => 0, 'end' => 0 ), $tableInclude=array(), $identifier='', $minimumDate=NULL )
|
||||
public function __construct( Url $url, string $table, string $dateField, string $title='', array $options=array(), string $defaultType='AreaChart', string $defaultTimescale='monthly', array $defaultTimes=array( 'start' => 0, 'end' => 0 ), array $tableInclude=array(), string $identifier='', DateTime $minimumDate=null )
|
||||
{
|
||||
$this->table = $table;
|
||||
$this->dateField = $dateField;
|
||||
$this->identifier = \substr( md5( $table . $dateField ), 0, 6 ) . $identifier . ( \IPS\Request::i()->chartId ?: '_default' );
|
||||
$this->identifier = substr( md5( $table . $dateField ), 0, 6 ) . $identifier . ( Request::i()->chartId ?: '_default' );
|
||||
|
||||
if ( !empty( $tableInclude ) )
|
||||
{
|
||||
@@ -112,24 +127,24 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
|
||||
parent::__construct( $url, $title, $options, $defaultType, $defaultTimescale, $defaultTimes, $identifier, $minimumDate );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add Series
|
||||
*
|
||||
* @param mixed $name Either a string with the series name or an array [ 'value' => "Series Name", 'key' => "XX" ]. Keys are used to give a country code for GeoCharts.
|
||||
* @param string $type Type of value
|
||||
* @param string $type Type of value
|
||||
* @li string
|
||||
* @li number
|
||||
* @li boolean
|
||||
* @li date
|
||||
* @li datetime
|
||||
* @li timeofday
|
||||
* @param string $sql SQL expression to get value
|
||||
* @param bool $filterable If TRUE, will show as a filter option to be toggled on/off
|
||||
* @param string $groupByKey If $this->groupBy is set, the raw key value
|
||||
* @param string $sql SQL expression to get value
|
||||
* @param bool $filterable If TRUE, will show as a filter option to be toggled on/off
|
||||
* @param string|null $groupByKey If $this->groupBy is set, the raw key value
|
||||
* @return void
|
||||
*/
|
||||
public function addSeries( $name, $type, $sql, $filterable=TRUE, $groupByKey=NULL )
|
||||
public function addSeries( mixed $name, string $type, string $sql, bool $filterable=TRUE, string $groupByKey=NULL ) : void
|
||||
{
|
||||
if ( $groupByKey !== NULL )
|
||||
{
|
||||
@@ -137,16 +152,16 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
}
|
||||
else
|
||||
{
|
||||
$name = \is_array( $name ) ? $name['value'] : $name;
|
||||
\IPS\Member::loggedIn()->language()->parseOutputForDisplay( $name );
|
||||
$name = is_array( $name ) ? $name['value'] : $name;
|
||||
Member::loggedIn()->language()->parseOutputForDisplay( $name );
|
||||
$filterKey = $name;
|
||||
}
|
||||
|
||||
if ( !$filterable or !isset( \IPS\Request::i()->filters[ $this->identifier ] ) or \in_array( $filterKey, \IPS\Request::i()->filters[ $this->identifier ] ) )
|
||||
|
||||
if ( !$filterable or !isset( Request::i()->filters[ $this->identifier ] ) or in_array( $filterKey, Request::i()->filters[ $this->identifier ] ) )
|
||||
{
|
||||
if ( $this->type !== 'PieChart' and $this->type !== 'GeoChart' )
|
||||
{
|
||||
$this->addHeader( \is_array( $name ) ? $name['value'] : $name, $type );
|
||||
$this->addHeader( is_array( $name ) ? $name['value'] : $name, $type );
|
||||
}
|
||||
|
||||
if( $this->groupBy )
|
||||
@@ -155,68 +170,68 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
}
|
||||
|
||||
$this->series[ $filterKey ] = $sql;
|
||||
|
||||
|
||||
if ( $filterable )
|
||||
{
|
||||
$this->currentFilters[] = $filterKey;
|
||||
|
||||
if ( isset( \IPS\Request::i()->filters[ $this->identifier ] ) )
|
||||
|
||||
if ( isset( Request::i()->filters[ $this->identifier ] ) )
|
||||
{
|
||||
$this->url = $this->url->setQueryString( 'filters', array( $this->identifier => $this->currentFilters ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ( $filterable )
|
||||
{
|
||||
$this->availableFilters[ $filterKey ] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Init the data array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function initData()
|
||||
protected function initData(): array
|
||||
{
|
||||
$data = parent::initData();
|
||||
|
||||
|
||||
if ( $this->customFiltersForm )
|
||||
{
|
||||
if ( $values = $this->getCustomFiltersForm()->values() )
|
||||
{
|
||||
$whereFunction = $this->customFiltersForm['where'];
|
||||
$seriesFunction = $this->customFiltersForm['series'];
|
||||
|
||||
|
||||
foreach( $values as $k => $v )
|
||||
{
|
||||
unset( $values[ $k ] );
|
||||
$values[ \mb_substr( $k, \mb_strlen( $this->identifier ) ) ] = $v;
|
||||
$values[ mb_substr( $k, mb_strlen( $this->identifier ) ) ] = $v;
|
||||
}
|
||||
|
||||
$this->groupBy = $this->customFiltersForm['groupBy'];
|
||||
|
||||
$whereFunctionResult = $whereFunction( $values );
|
||||
|
||||
$this->where[] = \is_array( $whereFunctionResult ) ? $whereFunctionResult : array( $whereFunctionResult );
|
||||
|
||||
|
||||
$this->where[] = is_array( $whereFunctionResult ) ? $whereFunctionResult : array( $whereFunctionResult );
|
||||
|
||||
foreach( $seriesFunction( $values ) as $series )
|
||||
{
|
||||
$this->addSeries( $series[0], $series[1], $series[2], $series[3], $series[4] );
|
||||
}
|
||||
}
|
||||
else if ( $this->savedCustomFilters and \count( $this->savedCustomFilters ) )
|
||||
else if ( $this->savedCustomFilters and count( $this->savedCustomFilters ) )
|
||||
{
|
||||
$whereFunction = $this->customFiltersForm['where'];
|
||||
$seriesFunction = $this->customFiltersForm['series'];
|
||||
|
||||
|
||||
$this->groupBy = $this->customFiltersForm['groupBy'];
|
||||
|
||||
$whereFunctionResult = $whereFunction( $this->savedCustomFilters );
|
||||
$this->where[] = \is_array( $whereFunctionResult ) ? $whereFunctionResult : array( $whereFunctionResult );
|
||||
|
||||
$this->where[] = is_array( $whereFunctionResult ) ? $whereFunctionResult : array( $whereFunctionResult );
|
||||
|
||||
foreach( $seriesFunction( $this->savedCustomFilters ) as $series )
|
||||
{
|
||||
$this->addSeries( $series[0], $series[1], $series[2], $series[3], $series[4] );
|
||||
@@ -268,12 +283,12 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
}
|
||||
|
||||
$fromUnixTime = "FROM_UNIXTIME( IFNULL( {$this->dateField}, 0 ) )";
|
||||
if ( !$this->timezoneError and \IPS\Member::loggedIn()->timezone and \in_array( \IPS\Member::loggedIn()->timezone, \DateTimeZone::listIdentifiers() ) )
|
||||
if ( !$this->timezoneError and Member::loggedIn()->timezone and in_array( Member::loggedIn()->timezone, DateTimeZone::listIdentifiers() ) )
|
||||
{
|
||||
$fromUnixTime = "CONVERT_TZ( {$fromUnixTime}, @@session.time_zone, '" . \IPS\Db::i()->escape_string( \IPS\Member::loggedIn()->timezone ) . "' )";
|
||||
$fromUnixTime = "CONVERT_TZ( {$fromUnixTime}, @@session.time_zone, '" . Db::i()->escape_string( Member::loggedIn()->timezone ) . "' )";
|
||||
}
|
||||
|
||||
$allRecordsStatement = \IPS\Db::i()->select(
|
||||
$allRecordsStatement = Db::i()->select(
|
||||
"SQL_BIG_RESULT DATE_FORMAT( {$fromUnixTime}, '{$timescale}' ) AS time_as_alias, COUNT(*) as $this->allRecordsLang",
|
||||
$this->table,
|
||||
$where,
|
||||
@@ -288,23 +303,22 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
$data[ $allRecordsRow['time_as_alias'] ][$this->allRecordsLang] = $allRecordsRow[$this->allRecordsLang];
|
||||
}
|
||||
|
||||
$this->addSeries( \IPS\Member::loggedIn()->language()->addToStack($this->allRecordsLang), 'number', 'COUNT(*)', FALSE, $this->allRecordsLang );
|
||||
$this->addSeries( Member::loggedIn()->language()->addToStack($this->allRecordsLang), 'number', 'COUNT(*)', FALSE, $this->allRecordsLang );
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compile Data for Output
|
||||
*
|
||||
* @param array $data The data
|
||||
* @return void
|
||||
* @return void
|
||||
*/
|
||||
public function compileForOutput()
|
||||
public function compileForOutput() : void
|
||||
{
|
||||
/* Init data */
|
||||
$data = $this->initData();
|
||||
|
||||
|
||||
/* Work out where clause */
|
||||
$where = $this->where;
|
||||
$where[] = array( "{$this->dateField}>?", 0 );
|
||||
@@ -316,7 +330,7 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
{
|
||||
$where[] = array( "{$this->dateField}<?", $this->end->getTimestamp() );
|
||||
}
|
||||
|
||||
|
||||
/* What's our SQL time? */
|
||||
switch ( $this->timescale )
|
||||
{
|
||||
@@ -327,23 +341,23 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
case 'daily':
|
||||
$timescale = '%Y-%c-%e';
|
||||
break;
|
||||
|
||||
|
||||
case 'weekly':
|
||||
$timescale = '%x-%v';
|
||||
break;
|
||||
|
||||
|
||||
case 'monthly':
|
||||
$timescale = '%Y-%c';
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
/* Pie Chart */
|
||||
if ( $this->type === 'PieChart' or $this->type === 'GeoChart' )
|
||||
{
|
||||
{
|
||||
$keys = array_unique( $this->series );
|
||||
$key = array_pop( $keys );
|
||||
|
||||
$stmt = \IPS\Db::i()->select(
|
||||
|
||||
$stmt = Db::i()->select(
|
||||
"{$key}" . ( $this->groupBy ? ", {$this->groupBy}" : '' ),
|
||||
$this->table,
|
||||
$where,
|
||||
@@ -352,11 +366,11 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
$this->groupBy
|
||||
);
|
||||
|
||||
if( \count( $this->joins ) )
|
||||
if( count( $this->joins ) )
|
||||
{
|
||||
foreach( $this->joins as $join )
|
||||
{
|
||||
$stmt = $stmt->join( $join[0], $join[1], ( isset( $join[2] ) ? $join[2] : 'LEFT' ), ( isset( $join[3] ) ? $join[3] : FALSE ) );
|
||||
$stmt = $stmt->join( $join[0], $join[1], ( $join[2] ?? 'LEFT' ), ( $join[3] ?? FALSE ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,26 +379,26 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
|
||||
foreach ( $stmt as $k => $v )
|
||||
{
|
||||
if( \count( $this->availableFilters ) and !\in_array( $k, $this->currentFilters ) )
|
||||
if( count( $this->availableFilters ) and !in_array( $k, $this->currentFilters ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
$this->addRow( array( 'key' => $this->availableFilters[ $k ], 'value' => $v ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Graph */
|
||||
else
|
||||
{
|
||||
/* Fetch */
|
||||
$fromUnixTime = "FROM_UNIXTIME( IFNULL( {$this->dateField}, 0 ) )";
|
||||
if ( !$this->timezoneError and \IPS\Member::loggedIn()->timezone and \in_array( \IPS\Member::loggedIn()->timezone, \DateTimeZone::listIdentifiers() ) )
|
||||
if ( !$this->timezoneError and Member::loggedIn()->timezone and in_array( Member::loggedIn()->timezone, DateTimeZone::listIdentifiers() ) )
|
||||
{
|
||||
$fromUnixTime = "CONVERT_TZ( {$fromUnixTime}, @@session.time_zone, '" . \IPS\Db::i()->escape_string( \IPS\Member::loggedIn()->timezone ) . "' )";
|
||||
$fromUnixTime = "CONVERT_TZ( {$fromUnixTime}, @@session.time_zone, '" . Db::i()->escape_string( Member::loggedIn()->timezone ) . "' )";
|
||||
}
|
||||
|
||||
$stmt = \IPS\Db::i()->select(
|
||||
"SQL_BIG_RESULT DATE_FORMAT( {$fromUnixTime}, '{$timescale}' ) AS time_as_alias" . ( \count( $this->series ) ? "," : "" ) . implode( ', ', array_unique( $this->series ) ) . ( $this->groupBy ? ", " . $this->groupBy : '' ),
|
||||
$stmt = Db::i()->select(
|
||||
"SQL_BIG_RESULT DATE_FORMAT( {$fromUnixTime}, '{$timescale}' ) AS time_as_alias" . ( count( $this->series ) ? "," : "" ) . implode( ', ', array_unique( $this->series ) ) . ( $this->groupBy ? ", " . $this->groupBy : '' ),
|
||||
$this->table,
|
||||
$where,
|
||||
'time_as_alias ASC',
|
||||
@@ -392,21 +406,21 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
$this->groupBy ? array( 'time_as_alias', $this->groupBy ) : 'time_as_alias'
|
||||
);
|
||||
|
||||
if( \count( $this->joins ) )
|
||||
if( count( $this->joins ) )
|
||||
{
|
||||
foreach( $this->joins as $join )
|
||||
{
|
||||
$stmt = $stmt->join( $join[0], $join[1], ( isset( $join[2] ) ? $join[2] : 'LEFT' ), ( isset( $join[3] ) ? $join[3] : FALSE ) );
|
||||
$stmt = $stmt->join( $join[0], $join[1], ( $join[2] ?? 'LEFT' ), ( $join[3] ?? FALSE ) );
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $stmt as $row )
|
||||
{
|
||||
$result = array();
|
||||
|
||||
|
||||
if( $this->groupBy )
|
||||
{
|
||||
if( \count( $this->availableFilters ) AND !\in_array( $row[ $this->groupBy ], $this->currentFilters ) )
|
||||
if( count( $this->availableFilters ) AND !in_array( $row[ $this->groupBy ], $this->currentFilters ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -431,29 +445,29 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
$result[ $column ] = $row[ $column ];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$data[ $row['time_as_alias'] ] = $result;
|
||||
}
|
||||
|
||||
ksort( $data, SORT_NATURAL );
|
||||
|
||||
|
||||
/* Add to graph */
|
||||
$min = NULL;
|
||||
$max = NULL;
|
||||
foreach ( $data as $time => $d )
|
||||
{
|
||||
$datetime = new \IPS\DateTime;
|
||||
$datetime = new DateTime;
|
||||
|
||||
if ( \IPS\Member::loggedIn()->timezone )
|
||||
if ( Member::loggedIn()->timezone )
|
||||
{
|
||||
try
|
||||
{
|
||||
$datetime->setTimezone( new \DateTimeZone( \IPS\Member::loggedIn()->timezone ) );
|
||||
$datetime->setTimezone( new DateTimeZone( Member::loggedIn()->timezone ) );
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
catch ( Exception $e )
|
||||
{
|
||||
\IPS\Member::loggedIn()->timezone = null;
|
||||
\IPS\Member::loggedIn()->save();
|
||||
Member::loggedIn()->timezone = null;
|
||||
Member::loggedIn()->save();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,26 +484,26 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
switch ( $this->timescale )
|
||||
{
|
||||
case 'none':
|
||||
$datetime = \IPS\DateTime::ts( $time );
|
||||
$datetime = DateTime::ts( $time );
|
||||
break;
|
||||
|
||||
case 'daily':
|
||||
$datetime->setDate( (float) $exploded[0], $exploded[1], $exploded[2] );
|
||||
//$datetime = $datetime->localeDate();
|
||||
break;
|
||||
|
||||
|
||||
case 'weekly':
|
||||
$datetime->setISODate( (float) $exploded[0], $exploded[1] );
|
||||
//$datetime = $datetime->localeDate();
|
||||
break;
|
||||
|
||||
|
||||
case 'monthly':
|
||||
$datetime->setDate( (float) $exploded[0], $exploded[1], 1 );
|
||||
//$datetime = $datetime->format( 'F Y' );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ( empty( $d ) )
|
||||
{
|
||||
if ( empty( $this->series ) )
|
||||
@@ -498,7 +512,7 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->addRow( array_merge( array( $datetime ), array_fill( 0, \count( $this->series ), 0 ) ) );
|
||||
$this->addRow( array_merge( array( $datetime ), array_fill( 0, count( $this->series ), 0 ) ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -510,14 +524,14 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
{
|
||||
$_values[ $id ] = ( isset( $d[ $id ] ) ) ? $d[ $id ] : ( $this->plotZeros ? 0 : NULL );
|
||||
}
|
||||
|
||||
|
||||
$this->addRow( array_merge( array( $datetime ), $_values ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( \count($d) < \count($this->series) )
|
||||
if( count($d) < count($this->series) )
|
||||
{
|
||||
$this->addRow( array_merge( array( $datetime ), $d, array_fill( 0, \count($this->series) - \count($d), 0 ) ) );
|
||||
$this->addRow( array_merge( array( $datetime ), $d, array_fill( 0, count($this->series) - count($d), 0 ) ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -526,20 +540,20 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( \count( $data ) === 1 )
|
||||
|
||||
if ( count( $data ) === 1 )
|
||||
{
|
||||
$this->options['domainAxis']['type'] = 'category';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the chart output
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOutput()
|
||||
public function getOutput(): string
|
||||
{
|
||||
/* Auto-support tables where appropriate */
|
||||
if ( !empty( $this->tableInclude ) )
|
||||
@@ -549,7 +563,7 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
$this->availableTypes[] = 'Table';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Work out where clause */
|
||||
$where = $this->where;
|
||||
$where[] = array( "{$this->dateField}>?", 0 );
|
||||
@@ -561,31 +575,31 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
{
|
||||
$where[] = array( "{$this->dateField}<?", $this->end->getTimestamp() );
|
||||
}
|
||||
|
||||
|
||||
/* Table... */
|
||||
if ( $this->type === 'Table' )
|
||||
{
|
||||
/* Are we filtering? */
|
||||
if( $this->groupBy )
|
||||
{
|
||||
if( \count( $this->availableFilters ) AND \count( $this->currentFilters ) )
|
||||
if( count( $this->availableFilters ) AND count( $this->currentFilters ) )
|
||||
{
|
||||
$where[] = array( \IPS\Db::i()->in( $this->groupBy, $this->currentFilters ) );
|
||||
$where[] = array( Db::i()->in( $this->groupBy, $this->currentFilters ) );
|
||||
}
|
||||
}
|
||||
|
||||
$table = new \IPS\Helpers\Table\Db( $this->table, $this->url, $where );
|
||||
$table = new TableDb( $this->table, $this->url, $where );
|
||||
|
||||
/* Reformat chart join for the table helper */
|
||||
if( \is_array( $this->joins ) )
|
||||
if( is_array( $this->joins ) )
|
||||
{
|
||||
array_walk( $this->joins, function ( $item ) use ( $table )
|
||||
{
|
||||
$table->joins[] = array('from' => $item[0], 'where' => $item[1], 'type' => isset( $item[2] ) ? $item[2] : 'LEFT');
|
||||
$table->joins[] = array('from' => $item[0], 'where' => $item[1], 'type' => $item[2] ?? 'LEFT' );
|
||||
} );
|
||||
}
|
||||
|
||||
if( \count( $this->tableInclude ) )
|
||||
if( count( $this->tableInclude ) )
|
||||
{
|
||||
$table->include = $this->tableInclude;
|
||||
}
|
||||
@@ -595,8 +609,8 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
}
|
||||
$table->parsers = $this->tableParsers;
|
||||
$table->sortBy = $table->sortBy ?: $this->dateField;
|
||||
|
||||
if ( isset( \IPS\Request::i()->download ) )
|
||||
|
||||
if ( isset( Request::i()->download ) )
|
||||
{
|
||||
$headers = array();
|
||||
foreach( $table->getHeaders( $table->getAdvancedSearchValues() ) AS $header )
|
||||
@@ -605,13 +619,13 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
$headers[] = array(
|
||||
'label' => \IPS\Member::loggedIn()->language()->addToStack( $header ),
|
||||
'label' => Member::loggedIn()->language()->addToStack( $header ),
|
||||
'type' => 'string'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
$rows = array();
|
||||
foreach( $table->getRows( $table->getAdvancedSearchValues() ) AS $k => $v )
|
||||
{
|
||||
@@ -621,20 +635,20 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
$rows[$k][] = trim( strip_tags( (string) $value ) );
|
||||
}
|
||||
}
|
||||
|
||||
return $this->download( $headers, $rows, \IPS\Output::i()->title );
|
||||
}
|
||||
|
||||
$output = (string) $table;
|
||||
|
||||
return $output;
|
||||
$this->download( $headers, $rows, Output::i()->title );
|
||||
return '';
|
||||
}
|
||||
|
||||
return (string) $table;
|
||||
}
|
||||
|
||||
|
||||
$this->compileForOutput();
|
||||
|
||||
if ( isset( \IPS\Request::i()->download ) )
|
||||
if ( isset( Request::i()->download ) )
|
||||
{
|
||||
return $this->download();
|
||||
$this->download();
|
||||
return '';
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+222
-188
@@ -10,167 +10,202 @@
|
||||
|
||||
namespace IPS\Helpers\Chart;
|
||||
|
||||
use IPS\Member;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use DateInterval;
|
||||
use DateTimeZone;
|
||||
use Exception;
|
||||
use IPS\core\Statistics\Chart as StatisticsChart;
|
||||
use IPS\DateTime;
|
||||
use IPS\Db;
|
||||
use IPS\Helpers\Chart;
|
||||
use IPS\Helpers\Form;
|
||||
use IPS\Helpers\Form\Date;
|
||||
use IPS\Helpers\Form\Text;
|
||||
use IPS\Http\Url;
|
||||
use IPS\IPS;
|
||||
use IPS\Member;
|
||||
use IPS\Output;
|
||||
use IPS\Request;
|
||||
use IPS\Session;
|
||||
use IPS\Theme;
|
||||
use Throwable;
|
||||
use function defined;
|
||||
use function file_get_contents;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
use function is_numeric;
|
||||
use function mb_substr;
|
||||
use const IPS\TEMP_DIRECTORY;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic Chart Helper
|
||||
*/
|
||||
abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
abstract class Dynamic extends Chart
|
||||
{
|
||||
/**
|
||||
* @brief URL
|
||||
*/
|
||||
public $url;
|
||||
public Url $url;
|
||||
|
||||
/**
|
||||
* @var Url|null
|
||||
*/
|
||||
public ?Url $baseURL = null;
|
||||
|
||||
/**
|
||||
* @brief $timescale (daily, weekly, monthly)
|
||||
*/
|
||||
public $timescale = 'monthly';
|
||||
public mixed $timescale = 'monthly';
|
||||
|
||||
/**
|
||||
* @brief Unique identifier for URLs
|
||||
*/
|
||||
public $identifier = '';
|
||||
public string $identifier = '';
|
||||
|
||||
/**
|
||||
* @brief Start Date
|
||||
*/
|
||||
public $start;
|
||||
public mixed $start = NULL;
|
||||
|
||||
/**
|
||||
* @brief End Date
|
||||
*/
|
||||
public $end;
|
||||
public mixed $end = NULL;
|
||||
|
||||
/**
|
||||
* @brief Series
|
||||
*/
|
||||
protected $series = array();
|
||||
protected array $series = array();
|
||||
|
||||
/**
|
||||
* @brief Title
|
||||
*/
|
||||
public $title;
|
||||
public ?string $title = NULL;
|
||||
|
||||
/**
|
||||
* @brief Description
|
||||
*/
|
||||
public $description;
|
||||
public ?string $description = NULL;
|
||||
|
||||
/**
|
||||
* @brief Google Chart Options
|
||||
*/
|
||||
public $options = array();
|
||||
public array $options = array();
|
||||
|
||||
/**
|
||||
* @brief Type
|
||||
*/
|
||||
public $type;
|
||||
public mixed $type;
|
||||
|
||||
/**
|
||||
* @brief Search term
|
||||
*/
|
||||
public $searchTerm = null;
|
||||
public mixed $searchTerm = null;
|
||||
|
||||
/**
|
||||
* @brief Available Types
|
||||
*/
|
||||
public $availableTypes = array( 'AreaChart', 'LineChart', 'ColumnChart', 'BarChart', 'PieChart', 'Table' );
|
||||
public array $availableTypes = array( 'AreaChart', 'LineChart', 'ColumnChart', 'BarChart', 'PieChart', 'Table' );
|
||||
|
||||
/**
|
||||
* @brief Available Filters
|
||||
*/
|
||||
public $availableFilters = array();
|
||||
public array $availableFilters = array();
|
||||
|
||||
/**
|
||||
* @brief Current Filters
|
||||
*/
|
||||
public $currentFilters = array();
|
||||
public array $currentFilters = array();
|
||||
|
||||
/**
|
||||
* @brief Plot zeros
|
||||
*/
|
||||
public $plotZeros = TRUE;
|
||||
public bool $plotZeros = TRUE;
|
||||
|
||||
/**
|
||||
* @brief Value for number formatter
|
||||
*/
|
||||
public $format = NULL;
|
||||
public mixed $format = NULL;
|
||||
|
||||
/**
|
||||
* @brief Allow user to adjust interval (group by daily, monthly, etc.)
|
||||
*/
|
||||
public $showIntervals = TRUE;
|
||||
public bool $showIntervals = TRUE;
|
||||
|
||||
/**
|
||||
* @brief Allow user to adjust date range
|
||||
*/
|
||||
public $showDateRange = TRUE;
|
||||
public bool $showDateRange = TRUE;
|
||||
|
||||
/**
|
||||
* @brief Show save button
|
||||
*/
|
||||
public $showSave = TRUE;
|
||||
public bool $showSave = TRUE;
|
||||
|
||||
/**
|
||||
* @brief If a warning about timezones needs to be shown
|
||||
*/
|
||||
public $timezoneError = FALSE;
|
||||
public bool $timezoneError = FALSE;
|
||||
|
||||
/**
|
||||
* @brief If we need to show a timezone warning, we usually include a link to learn more - set this to TRUE to hide the link
|
||||
*/
|
||||
public $hideTimezoneLink = FALSE;
|
||||
public bool $hideTimezoneLink = FALSE;
|
||||
|
||||
/**
|
||||
* @brief If set to an \IPS\DateTime instance, minimum time will be checked against this value
|
||||
* @brief If set to an DateTime instance, minimum time will be checked against this value
|
||||
*/
|
||||
public $minimumDate = NULL;
|
||||
public ?DateTime $minimumDate = NULL;
|
||||
|
||||
/**
|
||||
* @brief Enable hourly filtering. USE WITH CAUTION.
|
||||
*/
|
||||
public $enableHourly = FALSE;
|
||||
public bool $enableHourly = FALSE;
|
||||
|
||||
/**
|
||||
* @brief Error(s) to show on chart UI
|
||||
*/
|
||||
public $errors = array();
|
||||
public array $errors = array();
|
||||
|
||||
/**
|
||||
* @brief Saved custom filters
|
||||
*/
|
||||
public $savedCustomFilters = array();
|
||||
public array $savedCustomFilters = array();
|
||||
|
||||
/**
|
||||
* @brief Prefix passed to tables
|
||||
*/
|
||||
public string $tableLangPrefix = '';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \IPS\Http\Url $url The URL the chart will be displayed on
|
||||
* @param string $title Title
|
||||
* @param array $options Options
|
||||
* @param string $defaultType The default chart type
|
||||
* @param string $defaultTimescale The default timescale to use
|
||||
* @param array $defaultTimes The default start/end times to use
|
||||
* @param string $identifier If there will be more than one chart per page, provide a unique identifier
|
||||
* @param \IPS\DateTime|NULL $minimumDate The earliest available date for this chart
|
||||
* @see <a href='https://google-developers.appspot.com/chart/interactive/docs/gallery'>Charts Gallery - Google Charts - Google Developers</a>
|
||||
* @param Url $url The URL the chart will be displayed on
|
||||
* @param string $title Title
|
||||
* @param array $options Options
|
||||
* @param string $defaultType The default chart type
|
||||
* @param string $defaultTimescale The default timescale to use
|
||||
* @param array $defaultTimes The default start/end times to use
|
||||
* @param string $identifier If there will be more than one chart per page, provide a unique identifier
|
||||
* @param DateTime|null $minimumDate The earliest available date for this chart
|
||||
* @return void
|
||||
*@see <a href='https://google-developers.appspot.com/chart/interactive/docs/gallery'>Charts Gallery - Google Charts - Google Developers</a>
|
||||
*/
|
||||
public function __construct( \IPS\Http\Url $url, $title='', $options=array(), $defaultType='AreaChart', $defaultTimescale='monthly', $defaultTimes=array( 'start' => 0, 'end' => 0 ), $identifier='', $minimumDate=NULL )
|
||||
public function __construct( Url $url, string $title='', array $options=array(), string $defaultType='AreaChart', string $defaultTimescale='monthly', array $defaultTimes=array( 'start' => 0, 'end' => 0 ), string $identifier='', DateTime $minimumDate=null )
|
||||
{
|
||||
/* If we are deleting a chart, just do that now and redirect */
|
||||
if( isset( \IPS\Request::i()->deleteChart ) )
|
||||
if( isset( Request::i()->deleteChart ) )
|
||||
{
|
||||
\IPS\Session::i()->csrfCheck();
|
||||
\IPS\Db::i()->delete( 'core_saved_charts', array( 'chart_id=? and chart_member=?', \IPS\Request::i()->deleteChart, \IPS\Member::loggedIn()->member_id ) );
|
||||
\IPS\Output::i()->redirect( \IPS\Request::i()->url()->stripQueryString( array( 'deleteChart', 'chartId', 'csrfKey' ) ), 'chart_deleted' );
|
||||
Session::i()->csrfCheck();
|
||||
Db::i()->delete( 'core_saved_charts', array( 'chart_id=? and chart_member=?', Request::i()->deleteChart, Member::loggedIn()->member_id ) );
|
||||
Output::i()->redirect( Request::i()->url()->stripQueryString( array( 'deleteChart', 'chartId', 'csrfKey' ) ), 'chart_deleted' );
|
||||
}
|
||||
|
||||
if ( !isset( $options['chartArea'] ) )
|
||||
@@ -181,22 +216,22 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
);
|
||||
}
|
||||
|
||||
if( isset( \IPS\Request::i()->chartId ) AND \IPS\Request::i()->chartId != '_default' )
|
||||
if( isset( Request::i()->chartId ) AND Request::i()->chartId != '_default' )
|
||||
{
|
||||
$url = $url->setQueryString( 'chartId', \IPS\Request::i()->chartId );
|
||||
$url = $url->setQueryString( 'chartId', Request::i()->chartId );
|
||||
}
|
||||
|
||||
$this->baseURL = $url;
|
||||
$this->title = $title;
|
||||
$this->options = $options;
|
||||
$this->timescale = $defaultTimescale;
|
||||
$this->start = $defaultTimes['start'] ?: \IPS\DateTime::create()->sub( new \DateInterval('P6M') );
|
||||
$this->end = $defaultTimes['end'] ?: \IPS\DateTime::create();
|
||||
$this->start = $defaultTimes['start'] ?: DateTime::create()->sub( new DateInterval('P6M') );
|
||||
$this->end = $defaultTimes['end'] ?: DateTime::create();
|
||||
$this->minimumDate = $minimumDate;
|
||||
|
||||
if ( isset( \IPS\Request::i()->type[ $this->identifier ] ) and \in_array( \IPS\Request::i()->type[ $this->identifier ], $this->availableTypes ) )
|
||||
if ( isset( Request::i()->type[ $this->identifier ] ) and in_array( Request::i()->type[ $this->identifier ], $this->availableTypes ) )
|
||||
{
|
||||
$this->type = \IPS\Request::i()->type[ $this->identifier ];
|
||||
$this->type = Request::i()->type[ $this->identifier ];
|
||||
$url = $url->setQueryString( 'type', array( $this->identifier => $this->type ) );
|
||||
}
|
||||
else
|
||||
@@ -205,18 +240,18 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
}
|
||||
|
||||
/* Are we searching? The chart controller should inspect this property if it supports searching to limit the series it adds. */
|
||||
if( isset( $this->options['limitSearch'] ) AND isset( \IPS\Request::i()->search[ $this->identifier ] ) )
|
||||
if( isset( $this->options['limitSearch'] ) AND isset( Request::i()->search[ $this->identifier ] ) )
|
||||
{
|
||||
$this->searchTerm = \IPS\Request::i()->search[ $this->identifier ];
|
||||
$this->searchTerm = Request::i()->search[ $this->identifier ];
|
||||
}
|
||||
|
||||
/* Change timescale */
|
||||
if ( isset( \IPS\Request::i()->timescale[ $this->identifier ] ) and \in_array( \IPS\Request::i()->timescale[ $this->identifier ], array( 'hourly', 'daily', 'weekly', 'monthly' ) ) )
|
||||
if ( isset( Request::i()->timescale[ $this->identifier ] ) and in_array( Request::i()->timescale[ $this->identifier ], array( 'hourly', 'daily', 'weekly', 'monthly' ) ) )
|
||||
{
|
||||
if( \IPS\Request::i()->timescale[ $this->identifier ] != 'hourly' OR ( \IPS\Request::i()->timescale[ $this->identifier ] == 'hourly' AND $this->enableHourly === TRUE ) )
|
||||
if( Request::i()->timescale[ $this->identifier ] != 'hourly' OR ( Request::i()->timescale[ $this->identifier ] == 'hourly' AND $this->enableHourly === TRUE ) )
|
||||
{
|
||||
$this->timescale = \IPS\Request::i()->timescale[ $this->identifier ];
|
||||
$url = $url->setQueryString( 'timescale', array( $this->identifier => \IPS\Request::i()->timescale[ $this->identifier ] ) );
|
||||
$this->timescale = Request::i()->timescale[ $this->identifier ];
|
||||
$url = $url->setQueryString( 'timescale', array( $this->identifier => Request::i()->timescale[ $this->identifier ] ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,26 +262,26 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->addHeader( \IPS\Member::loggedIn()->language()->addToStack('date'), ( $this->timescale == 'none' OR $this->timescale == 'hourly' ) ? 'datetime' : 'date' );
|
||||
$this->addHeader( Member::loggedIn()->language()->addToStack('date'), ( $this->timescale == 'none' OR $this->timescale == 'hourly' ) ? 'datetime' : 'date' );
|
||||
}
|
||||
|
||||
if ( isset( \IPS\Request::i()->start[ $this->identifier ] ) )
|
||||
if ( isset( Request::i()->start[ $this->identifier ] ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
$originalStart = $this->start;
|
||||
|
||||
if( !\IPS\Request::i()->start[ $this->identifier ] )
|
||||
if( !Request::i()->start[ $this->identifier ] )
|
||||
{
|
||||
$this->start = 0;
|
||||
}
|
||||
elseif ( \is_numeric( \IPS\Request::i()->start[ $this->identifier ] ) )
|
||||
elseif ( is_numeric( Request::i()->start[ $this->identifier ] ) )
|
||||
{
|
||||
$this->start = \IPS\DateTime::ts( \IPS\Request::i()->start[ $this->identifier ] );
|
||||
$this->start = DateTime::ts( Request::i()->start[ $this->identifier ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->start = new \IPS\DateTime( \IPS\Helpers\Form\Date::_convertDateFormat( \IPS\Request::i()->start[ $this->identifier ] ), new \DateTimeZone( \IPS\Member::loggedIn()->timezone ) );
|
||||
$this->start = new DateTime( Date::_convertDateFormat( Request::i()->start[ $this->identifier ] ), new DateTimeZone( Member::loggedIn()->timezone ) );
|
||||
}
|
||||
|
||||
if( $this->minimumDate > $this->start )
|
||||
@@ -264,24 +299,24 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
$url = $url->setQueryString( 'start', array( $this->identifier => $this->start->getTimestamp() ) );
|
||||
}
|
||||
}
|
||||
catch ( \Exception $e ) {}
|
||||
catch ( Exception $e ) {}
|
||||
}
|
||||
|
||||
if ( isset( \IPS\Request::i()->end[ $this->identifier ] ) )
|
||||
if ( isset( Request::i()->end[ $this->identifier ] ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
if( !\IPS\Request::i()->end[ $this->identifier ] )
|
||||
if( !Request::i()->end[ $this->identifier ] )
|
||||
{
|
||||
$this->end = \IPS\DateTime::create();
|
||||
$this->end = DateTime::create();
|
||||
}
|
||||
elseif ( \is_numeric( \IPS\Request::i()->end[ $this->identifier ] ) )
|
||||
elseif ( is_numeric( Request::i()->end[ $this->identifier ] ) )
|
||||
{
|
||||
$this->end = \IPS\DateTime::ts( \IPS\Request::i()->end[ $this->identifier ] );
|
||||
$this->end = DateTime::ts( Request::i()->end[ $this->identifier ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->end = new \IPS\DateTime( \IPS\Helpers\Form\Date::_convertDateFormat( \IPS\Request::i()->end[ $this->identifier ] ), new \DateTimeZone( \IPS\Member::loggedIn()->timezone ) );
|
||||
$this->end = new DateTime( Date::_convertDateFormat( Request::i()->end[ $this->identifier ] ), new DateTimeZone( Member::loggedIn()->timezone ) );
|
||||
}
|
||||
|
||||
/* The end date should include items to the end of the day */
|
||||
@@ -289,86 +324,85 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
|
||||
$url = $url->setQueryString( 'end', array( $this->identifier => $this->end->getTimestamp() ) );
|
||||
}
|
||||
catch ( \Exception $e ) {}
|
||||
catch ( Exception $e ) {}
|
||||
}
|
||||
|
||||
if ( isset( \IPS\Request::i()->filters[ $this->identifier ] ) )
|
||||
if ( isset( Request::i()->filters[ $this->identifier ] ) )
|
||||
{
|
||||
$url = $url->setQueryString( 'filters', '' );
|
||||
}
|
||||
|
||||
$this->url = $url;
|
||||
|
||||
if ( \IPS\Member::loggedIn()->timezone and \in_array( \IPS\Member::loggedIn()->timezone, \DateTimeZone::listIdentifiers() ) )
|
||||
if ( Member::loggedIn()->timezone and in_array( Member::loggedIn()->timezone, DateTimeZone::listIdentifiers() ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
$r = \IPS\Db::i()->query( "SELECT TIMEDIFF( NOW(), CONVERT_TZ( NOW(), @@session.time_zone, '" . \IPS\Db::i()->escape_string( \IPS\Member::loggedIn()->timezone ) . "' ) );" )->fetch_row();
|
||||
$r = Db::i()->query( "SELECT TIMEDIFF( NOW(), CONVERT_TZ( NOW(), @@session.time_zone, '" . Db::i()->escape_string( Member::loggedIn()->timezone ) . "' ) );" )->fetch_row();
|
||||
if ( $r[0] === NULL )
|
||||
{
|
||||
$this->timezoneError = TRUE;
|
||||
}
|
||||
}
|
||||
catch ( \IPS\Db\Exception $e )
|
||||
catch ( Db\Exception $e )
|
||||
{
|
||||
$this->timezoneError = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/* If we have requested a saved chart, load its filters */
|
||||
if( isset( \IPS\Request::i()->chartId ) AND \IPS\Request::i()->chartId != '_default' AND !isset( \IPS\Request::i()->filters ) )
|
||||
if( isset( Request::i()->chartId ) AND Request::i()->chartId != '_default' AND !isset( Request::i()->filters ) )
|
||||
{
|
||||
foreach( $this->loadAvailableChartTabs() as $chart )
|
||||
{
|
||||
if( $chart['chart_id'] == \IPS\Request::i()->chartId )
|
||||
if( $chart['chart_id'] == Request::i()->chartId )
|
||||
{
|
||||
$filters = array();
|
||||
foreach( json_decode( $chart['chart_configuration'], true ) as $key => $value )
|
||||
{
|
||||
if ( \mb_substr( $key, 0, 11 ) == 'customform_' )
|
||||
if ( mb_substr( $key, 0, 11 ) == 'customform_' )
|
||||
{
|
||||
$this->savedCustomFilters[ \mb_substr( $key, 11 ) ] = $value;
|
||||
$this->savedCustomFilters[ mb_substr( $key, 11 ) ] = $value;
|
||||
}
|
||||
else
|
||||
{
|
||||
$filters[ $key ] = $value;
|
||||
}
|
||||
}
|
||||
\IPS\Request::i()->filters = array( $this->identifier => $filters );
|
||||
Request::i()->filters = array( $this->identifier => $filters );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach( \IPS\Request::i() as $key => $value )
|
||||
foreach( Request::i() as $key => $value )
|
||||
{
|
||||
if ( $value and \mb_substr( $key, 0, 11 ) === 'customform_' )
|
||||
if ( $value and mb_substr( $key, 0, 11 ) === 'customform_' )
|
||||
{
|
||||
$name = \mb_substr( preg_replace( '#' . $this->identifier . '#', '', $key ), 11 );
|
||||
$name = mb_substr( preg_replace( '#' . $this->identifier . '#', '', $key ), 11 );
|
||||
|
||||
$this->savedCustomFilters[ $name ] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compile Data for Output
|
||||
*
|
||||
* @param array $data The data
|
||||
* @return void
|
||||
* @return void
|
||||
*/
|
||||
abstract public function compileForOutput();
|
||||
abstract public function compileForOutput() : void;
|
||||
|
||||
/**
|
||||
* Get the chart output
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getOutput();
|
||||
abstract public function getOutput(): string;
|
||||
|
||||
/**
|
||||
* @brief Form to save filters
|
||||
*/
|
||||
public $form = NULL;
|
||||
public ?Form $form = NULL;
|
||||
|
||||
/**
|
||||
* HTML
|
||||
@@ -380,8 +414,8 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
try
|
||||
{
|
||||
/* Generate a form so we can save our filters as a new saved chart */
|
||||
$this->form = new \IPS\Helpers\Form;
|
||||
$this->form->class = 'ipsForm_vertical';
|
||||
$this->form = new Form;
|
||||
$this->form->class = 'ipsForm--vertical ipsForm--chart-custom-filters';
|
||||
|
||||
if ( $this->customFiltersForm )
|
||||
{
|
||||
@@ -395,102 +429,103 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
}
|
||||
|
||||
/* If we have a sub selector from a node form, add on the identifier and pass off to the form to show the ajax */
|
||||
if ( \IPS\Request::i()->_nodeSelectName )
|
||||
if ( Request::i()->_nodeSelectName )
|
||||
{
|
||||
\IPS\Request::i()->_nodeSelectName = $this->identifier . \IPS\Request::i()->_nodeSelectName;
|
||||
Request::i()->_nodeSelectName = $this->identifier . Request::i()->_nodeSelectName;
|
||||
return (string) $this->getCustomFiltersForm();
|
||||
}
|
||||
|
||||
|
||||
/* We have an existing chart ID */
|
||||
if( isset( \IPS\Request::i()->chartId ) AND \IPS\Request::i()->chartId != '_default' )
|
||||
if( isset( Request::i()->chartId ) AND Request::i()->chartId != '_default' )
|
||||
{
|
||||
$title = '';
|
||||
|
||||
foreach( $this->loadAvailableChartTabs() as $chart )
|
||||
{
|
||||
if( $chart['chart_id'] == \IPS\Request::i()->chartId )
|
||||
if( $chart['chart_id'] == Request::i()->chartId )
|
||||
{
|
||||
$title = $chart['chart_title'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$custom = array();
|
||||
$chartFilters = ( isset( \IPS\Request::i()->chartFilters ) and \IPS\Request::i()->chartFilters ) ? \IPS\Request::i()->chartFilters : array();
|
||||
$this->timescale = ( isset( \IPS\Request::i()->timescale ) and \IPS\Request::i()->timescale[ $this->identifier ] ) ? \IPS\Request::i()->timescale[ $this->identifier ] : $chart['chart_timescale'] ?? 'monthly';
|
||||
|
||||
foreach( \IPS\Request::i() as $key => $value )
|
||||
$chartFilters = ( isset( Request::i()->chartFilters ) and Request::i()->chartFilters ) ? Request::i()->chartFilters : array();
|
||||
$this->timescale = ( isset( Request::i()->timescale ) and Request::i()->timescale[ $this->identifier ] ) ? Request::i()->timescale[ $this->identifier ] : $chart['chart_timescale'] ?? 'monthly';
|
||||
|
||||
|
||||
foreach( Request::i() as $key => $value )
|
||||
{
|
||||
if ( \mb_substr( $key, 0, 11 ) === 'customform_' )
|
||||
if ( mb_substr( $key, 0, 11 ) === 'customform_' )
|
||||
{
|
||||
$custom[ preg_replace( '#' . $this->identifier . '#', '', $key ) ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$this->form->add( new \IPS\Helpers\Form\Text( 'custom_chart_title', $title, TRUE ) );
|
||||
|
||||
$this->form->add( new Text( 'custom_chart_title', $title, TRUE ) );
|
||||
|
||||
if( $values = $this->form->values() )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_saved_charts', array( 'chart_title' => $values['custom_chart_title'] ), array( 'chart_id=? AND chart_member=?', \IPS\Request::i()->chartId, \IPS\Member::loggedIn()->member_id ) );
|
||||
Db::i()->update( 'core_saved_charts', array( 'chart_title' => $values['custom_chart_title'] ), array( 'chart_id=? AND chart_member=?', Request::i()->chartId, Member::loggedIn()->member_id ) );
|
||||
|
||||
/* And then return the output we need */
|
||||
\IPS\Output::i()->json( array(
|
||||
Output::i()->json( array(
|
||||
'title' => $values['custom_chart_title']
|
||||
) );
|
||||
}
|
||||
|
||||
/* And we want to save our filter updates */
|
||||
if( isset( \IPS\Request::i()->saveFilters ) )
|
||||
if( isset( Request::i()->saveFilters ) )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_saved_charts', array( 'chart_configuration' => json_encode( array_merge( $chartFilters, $custom ) ), 'chart_timescale' => \IPS\Request::i()->timescale ?? $this->timescale ?? 'monthly' ), array( 'chart_id=? AND chart_member=?', \IPS\Request::i()->chartId, \IPS\Member::loggedIn()->member_id ) );
|
||||
Db::i()->update( 'core_saved_charts', array( 'chart_configuration' => json_encode( array_merge( $chartFilters, $custom ) ), 'chart_timescale' => Request::i()->timescale ?? $this->timescale ?? 'monthly' ), array( 'chart_id=? AND chart_member=?', Request::i()->chartId, Member::loggedIn()->member_id ) );
|
||||
}
|
||||
}
|
||||
/* We are not viewing a saved chart */
|
||||
else
|
||||
{
|
||||
$this->form->add( new \IPS\Helpers\Form\Text( 'custom_chart_title', NULL, TRUE ) );
|
||||
|
||||
$this->form->add( new Text( 'custom_chart_title', NULL, TRUE ) );
|
||||
|
||||
if( $values = $this->form->values() )
|
||||
{
|
||||
$custom = array();
|
||||
foreach( \IPS\Request::i() as $key => $value )
|
||||
foreach( Request::i() as $key => $value )
|
||||
{
|
||||
if ( \mb_substr( $key, 0, 11 ) === 'customform_' )
|
||||
if ( mb_substr( $key, 0, 11 ) === 'customform_' )
|
||||
{
|
||||
$custom[ preg_replace( '#' . $this->identifier . '#', '', $key ) ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$chartFilters = ( isset( \IPS\Request::i()->chartFilters ) and \IPS\Request::i()->chartFilters ) ? \IPS\Request::i()->chartFilters : array();
|
||||
|
||||
|
||||
$chartFilters = ( isset( Request::i()->chartFilters ) and Request::i()->chartFilters ) ? Request::i()->chartFilters : array();
|
||||
|
||||
/* Store the new chart */
|
||||
$id = \IPS\Db::i()->insert( 'core_saved_charts', array(
|
||||
'chart_member' => \IPS\Member::loggedIn()->member_id,
|
||||
'chart_controller' => \IPS\Request::i()->app . '_' . \IPS\Request::i()->module . '_' . \IPS\Request::i()->controller . ( \IPS\Request::i()->tab ? '_' . \IPS\Request::i()->tab : '' ),
|
||||
$id = Db::i()->insert( 'core_saved_charts', array(
|
||||
'chart_member' => Member::loggedIn()->member_id,
|
||||
'chart_controller' => Request::i()->app . '_' . Request::i()->module . '_' . Request::i()->controller . ( Request::i()->tab ? '_' . Request::i()->tab : '' ),
|
||||
'chart_configuration' => json_encode( array_merge( $chartFilters, $custom ) ),
|
||||
'chart_timescale' => \IPS\Request::i()->timescale ?? $this->timescale ?? 'monthly',
|
||||
'chart_timescale' => Request::i()->timescale ?? $this->timescale ?? 'monthly',
|
||||
'chart_title' => $values['custom_chart_title'],
|
||||
) );
|
||||
|
||||
/* Set some input parameters */
|
||||
$this->url = $this->url->setQueryString( 'chartId', $id );
|
||||
\IPS\Request::i()->chartId = $id;
|
||||
\IPS\Request::i()->filters = array( $this->identifier => \IPS\Request::i()->chartFilters );
|
||||
Request::i()->chartId = $id;
|
||||
Request::i()->filters = array( $this->identifier => Request::i()->chartFilters );
|
||||
|
||||
$this->currentFilters = \IPS\Request::i()->filters;
|
||||
|
||||
if ( isset( \IPS\Request::i()->filters[ $this->identifier ] ) )
|
||||
$this->currentFilters = Request::i()->filters;
|
||||
|
||||
if ( isset( Request::i()->filters[ $this->identifier ] ) )
|
||||
{
|
||||
$this->url = $this->url->setQueryString( 'filters', array( $this->identifier => $this->currentFilters ) );
|
||||
}
|
||||
|
||||
/* Reset form, since template looks for it and it should not be set for a saved chart */
|
||||
$this->form = new \IPS\Helpers\Form;
|
||||
$this->form->class = 'ipsForm_vertical';
|
||||
$this->form->add( new \IPS\Helpers\Form\Text( 'custom_chart_title', $values['custom_chart_title'], TRUE ) );
|
||||
$this->form = new Form;
|
||||
$this->form->class = 'ipsForm--vertical ipsForm--chart-title';
|
||||
$this->form->add( new Text( 'custom_chart_title', $values['custom_chart_title'], TRUE ) );
|
||||
|
||||
/* And then return the output we need */
|
||||
\IPS\Output::i()->json( array(
|
||||
Output::i()->json( array(
|
||||
'tabHref' => $this->url->stripQueryString( 'filters' ),
|
||||
'chartId' => $id,
|
||||
'tabId' => md5( $this->url->acpQueryString() ),
|
||||
@@ -506,27 +541,27 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
}
|
||||
else
|
||||
{
|
||||
$output = \IPS\Member::loggedIn()->language()->addToStack('chart_no_results');
|
||||
$output = Member::loggedIn()->language()->addToStack('chart_no_results');
|
||||
}
|
||||
|
||||
/* Display */
|
||||
if ( \IPS\Request::i()->noheader )
|
||||
if ( Request::i()->noheader )
|
||||
{
|
||||
return $output;
|
||||
}
|
||||
else
|
||||
{
|
||||
$chartOutput = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->dynamicChart( $this, $output );
|
||||
|
||||
$chartOutput = Theme::i()->getTemplate( 'global', 'core', 'global' )->dynamicChart( $this, $output );
|
||||
|
||||
/* If we're not showing filter tabs, just return here. */
|
||||
if ( !$this->showFilterTabs )
|
||||
{
|
||||
return $chartOutput;
|
||||
}
|
||||
|
||||
if( !\IPS\Request::i()->isAjax() OR ( \IPS\Request::i()->tab AND !\IPS\Request::i()->chartId ) )
|
||||
if( !Request::i()->isAjax() OR ( Request::i()->tab AND !Request::i()->chartId ) )
|
||||
{
|
||||
return \IPS\Theme::i()->getTemplate( 'global', 'core' )->tabs( $this->getChartTabs(), ( isset( \IPS\Request::i()->chartId ) ) ? \IPS\Request::i()->chartId : NULL, $chartOutput, $this->url, 'chartId', 'ipsTabs_small ipsTabs_contained' );
|
||||
return Theme::i()->getTemplate( 'global', 'core' )->tabs( $this->getChartTabs(), ( isset( Request::i()->chartId ) ) ? Request::i()->chartId : NULL, $chartOutput, $this->url, 'chartId', 'ipsTabs_small ipsTabs_contained' );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -534,37 +569,34 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
}
|
||||
}
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
catch ( Exception|Throwable $e )
|
||||
{
|
||||
\IPS\IPS::exceptionHandler( $e );
|
||||
}
|
||||
catch ( \Throwable $e )
|
||||
{
|
||||
\IPS\IPS::exceptionHandler( $e );
|
||||
IPS::exceptionHandler( $e );
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Show filter tabs
|
||||
*/
|
||||
public $showFilterTabs = TRUE;
|
||||
public bool $showFilterTabs = TRUE;
|
||||
|
||||
/**
|
||||
* @brief Cached tab data
|
||||
*/
|
||||
protected $availableChartTabs = NULL;
|
||||
|
||||
protected ?array $availableChartTabs = NULL;
|
||||
|
||||
/**
|
||||
* @Brief Extension
|
||||
*/
|
||||
public $extension = NULL;
|
||||
public ?StatisticsChart $extension = NULL;
|
||||
|
||||
/**
|
||||
* Retrieve tabs based on saved charts
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getChartTabs()
|
||||
protected function getChartTabs(): array
|
||||
{
|
||||
$tabs = array( '_default' => 'dynamic_chart_overview' );
|
||||
|
||||
@@ -579,49 +611,49 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
/**
|
||||
* Load and return available chart tabs
|
||||
*
|
||||
* @return array
|
||||
* @return array|null
|
||||
*/
|
||||
protected function loadAvailableChartTabs()
|
||||
protected function loadAvailableChartTabs() : ?array
|
||||
{
|
||||
if( $this->availableChartTabs === NULL )
|
||||
if ( $this->availableChartTabs === null )
|
||||
{
|
||||
if ( $this->extension !== NULL )
|
||||
if ( $this->extension !== null )
|
||||
{
|
||||
$this->availableChartTabs = iterator_to_array( \IPS\Db::i()->select( '*', 'core_saved_charts', array( 'chart_member=? AND chart_controller=?', \IPS\Member::loggedIn()->member_id, $this->extension->controller ) ) );
|
||||
$this->availableChartTabs = iterator_to_array( Db::i()->select( '*', 'core_saved_charts', ['chart_member=? AND chart_controller=?', Member::loggedIn()->member_id, $this->extension->controller] ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->availableChartTabs = iterator_to_array( \IPS\Db::i()->select( '*', 'core_saved_charts', array( 'chart_member=? AND chart_controller=?', \IPS\Member::loggedIn()->member_id, \IPS\Request::i()->app . '_' . \IPS\Request::i()->module . '_' . \IPS\Request::i()->controller . ( \IPS\Request::i()->tab ? '_' . \IPS\Request::i()->tab : '' ) ) ) );
|
||||
$this->availableChartTabs = iterator_to_array( Db::i()->select( '*', 'core_saved_charts', ['chart_member=? AND chart_controller=?', Member::loggedIn()->member_id, Request::i()->app . '_' . Request::i()->module . '_' . Request::i()->controller . ( Request::i()->tab ? '_' . Request::i()->tab : '' )] ) );
|
||||
}
|
||||
}
|
||||
|
||||
return $this->availableChartTabs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set extension
|
||||
*
|
||||
* @param \IPS\core\Statistics\Chart $ext Extension
|
||||
* @param StatisticsChart $ext Extension
|
||||
* @return void
|
||||
*/
|
||||
public function setExtension( \IPS\core\Statistics\Chart $ext )
|
||||
public function setExtension( StatisticsChart $ext ) : void
|
||||
{
|
||||
$this->extension = $ext;
|
||||
|
||||
|
||||
$this->availableChartTabs = NULL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Flip URL Filter
|
||||
*
|
||||
* @param string $filter The Filter
|
||||
* @return \IPS\Http\Url
|
||||
* @param string $filter The Filter
|
||||
* @return Url
|
||||
*/
|
||||
public function flipUrlFilter( $filter )
|
||||
public function flipUrlFilter( string $filter ): Url
|
||||
{
|
||||
$filters = $this->currentFilters;
|
||||
|
||||
if ( \in_array( $filter, $filters ) )
|
||||
if ( in_array( $filter, $filters ) )
|
||||
{
|
||||
unset( $filters[ array_search( $filter, $filters ) ] );
|
||||
}
|
||||
@@ -638,7 +670,7 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function initData()
|
||||
protected function initData(): array
|
||||
{
|
||||
/* Init data */
|
||||
$data = array();
|
||||
@@ -652,13 +684,13 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
case 'hourly':
|
||||
$data[ $date->format( 'Y-n-j-h-i-s' ) ] = array();
|
||||
|
||||
$date->add( new \DateInterval( 'PT1H' ) );
|
||||
$date->add( new DateInterval( 'PT1H' ) );
|
||||
break;
|
||||
|
||||
case 'daily':
|
||||
$data[ $date->format( 'Y-n-j' ) ] = array();
|
||||
|
||||
$date->add( new \DateInterval( 'P1D' ) );
|
||||
$date->add( new DateInterval( 'P1D' ) );
|
||||
break;
|
||||
|
||||
case 'weekly':
|
||||
@@ -666,13 +698,13 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
@see http://php.net/manual/en/function.date.php#106974 */
|
||||
$data[ $date->format( 'o-W' ) ] = array();
|
||||
|
||||
$date->add( new \DateInterval( 'P7D' ) );
|
||||
$date->add( new DateInterval( 'P7D' ) );
|
||||
break;
|
||||
|
||||
case 'monthly':
|
||||
$data[ $date->format( 'Y-n' ) ] = array();
|
||||
|
||||
$date->add( new \DateInterval( 'P1M' ) );
|
||||
$date->add( new DateInterval( 'P1M' ) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -684,16 +716,16 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
/**
|
||||
* Custom filter form
|
||||
*
|
||||
* @return \IPS\Helpers\Form
|
||||
* @return Form
|
||||
*/
|
||||
public function getCustomFiltersForm()
|
||||
public function getCustomFiltersForm(): Form
|
||||
{
|
||||
$customForm = new \IPS\Helpers\Form('filter_form', 'chart_customfilters_save');
|
||||
$customForm->class = 'ipsForm_vertical';
|
||||
$customForm = new Form('filter_form', 'chart_customfilters_save');
|
||||
$customForm->class = 'ipsForm--vertical ipsForm--custom-filter-form';
|
||||
|
||||
if ( isset( \IPS\Request::i()->chartId ) and \IPS\Request::i()->chartId != '_default' )
|
||||
if ( isset( Request::i()->chartId ) and Request::i()->chartId != '_default' )
|
||||
{
|
||||
$customForm->hiddenValues['chartId'] = \IPS\Request::i()->chartId;
|
||||
$customForm->hiddenValues['chartId'] = Request::i()->chartId;
|
||||
}
|
||||
|
||||
foreach( $this->customFiltersForm['form'] as $field )
|
||||
@@ -702,26 +734,29 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
{
|
||||
$langKey = $field->name;
|
||||
$field->name = $this->identifier . $field->name;
|
||||
\IPS\Member::loggedIn()->language()->words[ $field->name ] = \IPS\Member::loggedIn()->language()->get( $langKey );
|
||||
if( Member::loggedIn()->language()->checkKeyExists( $field->name ) )
|
||||
{
|
||||
Member::loggedIn()->language()->words[ $field->name ] = Member::loggedIn()->language()->get( $langKey );
|
||||
}
|
||||
}
|
||||
$customForm->add( $field );
|
||||
}
|
||||
|
||||
return $customForm;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Download as CSV
|
||||
*
|
||||
* @param array|NULL $headers Headers, or NULL to use headers defined by this object.
|
||||
* @param array|NULL $rows Rows, or NULL to use rows defined by this object.
|
||||
* @param string|NULL $fileName File Name, or NULL to use the title defined by this object.
|
||||
* @return void
|
||||
* @param array|null $rawHeaders
|
||||
* @param array|null $rawRows
|
||||
* @param string|NULL $fileName File Name, or NULL to use the title defined by this object.
|
||||
* @return void
|
||||
*/
|
||||
public function download( ?array $rawHeaders = NULL, ?array $rawRows = NULL, ?string $fileName = NULL )
|
||||
public function download( ?array $rawHeaders = NULL, ?array $rawRows = NULL, ?string $fileName = NULL ) : void
|
||||
{
|
||||
/* Compile the data */
|
||||
$file = tempnam( \IPS\TEMP_DIRECTORY, 'IPS' );
|
||||
$file = tempnam( TEMP_DIRECTORY, 'IPS' );
|
||||
$fh = fopen( $file, 'w' );
|
||||
|
||||
/* Set headers */
|
||||
@@ -751,7 +786,6 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
fputcsv( $fh, $headers );
|
||||
|
||||
/* Set Rows */
|
||||
|
||||
if ( $rawRows === NULL )
|
||||
{
|
||||
$rawRows = $this->rows;
|
||||
@@ -774,12 +808,12 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
case 'date':
|
||||
case 'datetime':
|
||||
case 'timeofday':
|
||||
$save[] = ( new \IPS\DateTime( $row[$i] ) )->fullYearLocaleDate();
|
||||
$save[] = ( new DateTime( $row[$i] ) )->fullYearLocaleDate();
|
||||
break;
|
||||
|
||||
/* Anything else can just save as-is */
|
||||
default:
|
||||
if ( \is_array( $row[$i] ) )
|
||||
if ( is_array( $row[$i] ) )
|
||||
{
|
||||
/* GeoCharts use arrays so we need to use the value rather */
|
||||
$save[] = $row[$i]['value'];
|
||||
@@ -796,7 +830,7 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
}
|
||||
|
||||
fclose( $fh );
|
||||
$csv = \file_get_contents( $file );
|
||||
$csv = file_get_contents( $file );
|
||||
if ( $fileName )
|
||||
{
|
||||
$name = $fileName;
|
||||
@@ -807,10 +841,10 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
}
|
||||
else
|
||||
{
|
||||
$name = \IPS\Output::i()->title;
|
||||
$name = Output::i()->title;
|
||||
}
|
||||
\IPS\Member::loggedIn()->language()->parseOutputForDisplay( $name );
|
||||
\IPS\Member::loggedIn()->language()->parseOutputForDisplay( $csv );
|
||||
\IPS\Output::i()->sendOutput( $csv, 200, 'text/csv', array( 'Content-Disposition' => \IPS\Output::getContentDisposition( 'attachment', "{$name}.csv" ) ), FALSE, FALSE, FALSE );
|
||||
Member::loggedIn()->language()->parseOutputForDisplay( $name );
|
||||
Member::loggedIn()->language()->parseOutputForDisplay( $csv );
|
||||
Output::i()->sendOutput( $csv, 200, 'text/csv', array( 'Content-Disposition' => Output::getContentDisposition( 'attachment', "{$name}.csv" ) ), FALSE, FALSE, FALSE );
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user