Version 4.5.0

This commit is contained in:
Neo committed 2025-12-19 05:51:58 -08:00
1 parent 1a0c7fd3c2
commit f79dcf067a
3791 files changed
+248032 -76372

No files matched your search

+35 -22
View File
@@ -11,7 +11,7 @@
namespace IPS\Helpers\Chart;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
@@ -46,7 +46,7 @@ class _Callback extends \IPS\Helpers\Chart\Dynamic
*/
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 )
{
$this->identifier = \substr( md5( (string) $url ), 0, 6 ) . $identifier;
$this->identifier = \substr( md5( (string) $url ), 0, 6 ) . $identifier . ( \IPS\Request::i()->chartId ?: '_default' );
$this->callback = $callback;
parent::__construct( $url, $title, $options, $defaultType, $defaultTimescale, $defaultTimes, $identifier, $minimumDate );
@@ -71,7 +71,7 @@ class _Callback extends \IPS\Helpers\Chart\Dynamic
\IPS\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( \IPS\Request::i()->filters[ $this->identifier ] ) or \in_array( $filterKey, \IPS\Request::i()->filters[ $this->identifier ] ) )
{
if ( $this->type !== 'PieChart' and $this->type !== 'GeoChart' )
{
@@ -96,40 +96,39 @@ class _Callback extends \IPS\Helpers\Chart\Dynamic
$this->availableFilters[ $filterKey ] = $name;
}
}
/**
* Get the chart output
* Compile Data for Output
*
* @return string
* @param array $data The data
* @return void
*/
public function getOutput()
public function compileForOutput(): void
{
/* Init data */
$data = $this->initData();
/* Get rows */
$callbackFunction = $this->callback;
$rows = $callbackFunction( $this );
/* Pie Chart */
if ( $this->type === 'PieChart' or $this->type === 'GeoChart' )
{
$rows = call_user_func( $this->callback, $this );
{
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;
}
$this->addRow( array( 'key' => $this->availableFilters[ $k ], 'value' => $v ) );
}
$output = $this->render( $this->type, $this->options, $this->format );
}
/* Graph */
else
{
$rows = call_user_func( $this->callback, $this );
foreach ( $rows as $row )
{
$result = array();
@@ -205,14 +204,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
{
@@ -221,14 +220,28 @@ class _Callback extends \IPS\Helpers\Chart\Dynamic
}
}
if ( count( $data ) === 1 )
if ( \count( $data ) === 1 )
{
$this->options['domainAxis']['type'] = 'category';
}
$output = $this->render( $this->type, $this->options, $this->format );
}
}
return $output;
/**
* Get the chart output
*
* @return string
*/
public function getOutput()
{
$this->compileForOutput();
if ( isset( \IPS\Request::i()->download ) )
{
return $this->download();
}
else
{
return $this->render( $this->type, $this->options, $this->format );
}
}
}
+8 -8
View File
@@ -11,7 +11,7 @@
namespace IPS\Helpers;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
@@ -84,7 +84,7 @@ class _Chart
*/
public function addRow( $values )
{
if ( count( $values ) !== count( $this->headers ) )
if ( \count( $values ) !== \count( $this->headers ) )
{
throw new \LengthException('COLUMN_COUNT_MISMATCH');
}
@@ -96,21 +96,21 @@ class _Chart
switch ( $data['type'] )
{
case 'string':
if ( !is_string( $values[ $i ] ) )
if ( !\is_string( $values[ $i ] ) )
{
throw new \InvalidArgumentException( "VALUE_{$i}_NOT_STRING" );
}
break;
case 'number':
if ( !is_numeric( $values[ $i ] ) and !is_null( $values[ $i ] ) )
if ( !\is_numeric( $values[ $i ] ) and !\is_null( $values[ $i ] ) )
{
throw new \InvalidArgumentException( "VALUE_{$i}_NOT_NUMBER" );
}
break;
case 'bool':
if ( !is_bool( $values[ $i ] ) )
if ( !\is_bool( $values[ $i ] ) )
{
throw new \InvalidArgumentException( "VALUE_{$i}_NOT_BOOL" );
}
@@ -146,11 +146,11 @@ class _Chart
{
if ( !\IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->jsFiles[] = 'https://www.google.com/jsapi';
\IPS\Output::i()->headJs .= "google.load('visualization', '1.0', {'packages':['corechart']}); ";
\IPS\Output::i()->jsFiles[] = 'https://www.gstatic.com/charts/loader.js';
\IPS\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;
}
+119 -64
View File
@@ -11,7 +11,7 @@
namespace IPS\Helpers\Chart;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
@@ -83,7 +83,7 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
{
$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 . ( \IPS\Request::i()->chartId ?: '_default' );
if ( !empty( $tableInclude ) )
{
@@ -121,7 +121,7 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
$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( \IPS\Request::i()->filters[ $this->identifier ] ) or \in_array( $filterKey, \IPS\Request::i()->filters[ $this->identifier ] ) )
{
if ( $this->type !== 'PieChart' and $this->type !== 'GeoChart' )
{
@@ -153,21 +153,13 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
}
/**
* Get the chart output
* Compile Data for Output
*
* @return string
* @param array $data The data
* @return void
*/
public function getOutput()
public function compileForOutput(): void
{
/* Auto-support tables where appropriate */
if ( !empty( $this->tableInclude ) )
{
if( !array_search( 'Table', $this->availableTypes ) )
{
$this->availableTypes[] = 'Table';
}
}
/* Init data */
$data = $this->initData();
@@ -195,7 +187,7 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
break;
case 'weekly':
$timescale = '%Y-%u';
$timescale = '%x-%v';
break;
case 'monthly':
@@ -203,40 +195,6 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
break;
}
/* Table... */
if ( $this->type === 'Table' )
{
/* Are we filtering? */
if( $this->groupBy )
{
if( count( $this->availableFilters ) AND count( $this->currentFilters ) )
{
$where[] = array( \IPS\Db::i()->in( $this->groupBy, $this->currentFilters ) );
}
}
$table = new \IPS\Helpers\Table\Db( $this->table, $this->url, $where );
/* Reformat chart join for the table helper */
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');
} );
}
if( count( $this->tableInclude ) )
{
$table->include = $this->tableInclude;
}
$table->parsers = $this->tableParsers;
$table->sortBy = $table->sortBy ?: $this->dateField;
$output = (string) $table;
return $output;
}
/* Pie Chart */
if ( $this->type === 'PieChart' or $this->type === 'GeoChart' )
{
@@ -252,7 +210,7 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
$this->groupBy
)->setKeyField( $this->groupBy )->setValueField( $key );
if( count( $this->joins ) )
if( \count( $this->joins ) )
{
foreach( $this->joins as $join )
{
@@ -262,14 +220,12 @@ 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 ) );
}
$output = $this->render( $this->type, $this->options, $this->format );
}
/* Graph */
@@ -277,7 +233,7 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
{
/* 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 \IPS\Member::loggedIn()->timezone and \in_array( \IPS\Member::loggedIn()->timezone, \DateTimeZone::listIdentifiers() ) )
{
$fromUnixTime = "CONVERT_TZ( {$fromUnixTime}, @@session.time_zone, '" . \IPS\Db::i()->escape_string( \IPS\Member::loggedIn()->timezone ) . "' )";
}
@@ -290,7 +246,7 @@ 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 )
{
@@ -304,7 +260,7 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
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;
}
@@ -396,7 +352,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
@@ -413,9 +369,9 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
}
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
{
@@ -425,14 +381,113 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
}
}
if ( count( $data ) === 1 )
if ( \count( $data ) === 1 )
{
$this->options['domainAxis']['type'] = 'category';
}
$output = $this->render( $this->type, $this->options, $this->format );
}
}
/**
* Get the chart output
*
* @return string
*/
public function getOutput()
{
/* Auto-support tables where appropriate */
if ( !empty( $this->tableInclude ) )
{
if( !array_search( 'Table', $this->availableTypes ) )
{
$this->availableTypes[] = 'Table';
}
}
/* Work out where clause */
$where = $this->where;
$where[] = array( "{$this->dateField}>?", 0 );
if ( $this->start )
{
$where[] = array( "{$this->dateField}>?", $this->start->getTimestamp() );
}
if ( $this->end )
{
$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 ) )
{
$where[] = array( \IPS\Db::i()->in( $this->groupBy, $this->currentFilters ) );
}
}
return $output;
$table = new \IPS\Helpers\Table\Db( $this->table, $this->url, $where );
/* Reformat chart join for the table helper */
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');
} );
}
if( \count( $this->tableInclude ) )
{
$table->include = $this->tableInclude;
}
$table->parsers = $this->tableParsers;
$table->sortBy = $table->sortBy ?: $this->dateField;
if ( isset( \IPS\Request::i()->download ) )
{
$headers = array();
foreach( $table->getHeaders( $table->getAdvancedSearchValues() ) AS $header )
{
if ( $header === '_buttons' )
{
continue;
}
$headers[] = array(
'label' => \IPS\Member::loggedIn()->language()->addToStack( $header ),
'type' => 'string'
);
}
$rows = array();
foreach( $table->getRows( $table->getAdvancedSearchValues() ) AS $k => $v )
{
$rows[$k] = array();
foreach( $v AS $j => $value )
{
$rows[$k][] = trim( strip_tags( (string) $value ) );
}
}
return $this->download( $headers, $rows, \IPS\Output::i()->title );
}
$output = (string) $table;
return $output;
}
$this->compileForOutput();
if ( isset( \IPS\Request::i()->download ) )
{
return $this->download();
}
else
{
return $this->render( $this->type, $this->options, $this->format );
}
}
}
+131 -14
View File
@@ -11,7 +11,7 @@
namespace IPS\Helpers\Chart;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
@@ -66,6 +66,11 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
* @brief Type
*/
public $type;
/**
* @brief Search term
*/
public $searchTerm = null;
/**
* @brief Available Types
@@ -102,6 +107,11 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
*/
public $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;
/**
* @brief If set to an \IPS\DateTime instance, minimum time will be checked against this value
*/
@@ -157,11 +167,11 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
$this->title = $title;
$this->options = $options;
$this->timescale = $defaultTimescale;
$this->start = $defaultTimes['start'];
$this->end = $defaultTimes['end'];
$this->start = $defaultTimes['start'] ?: \IPS\DateTime::create()->sub( new \DateInterval('P6M') );
$this->end = $defaultTimes['end'] ?: \IPS\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( \IPS\Request::i()->type[ $this->identifier ] ) and \in_array( \IPS\Request::i()->type[ $this->identifier ], $this->availableTypes ) )
{
$this->type = \IPS\Request::i()->type[ $this->identifier ];
$url = $url->setQueryString( 'type', array( $this->identifier => $this->type ) );
@@ -171,7 +181,14 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
$this->type = $defaultType;
}
if ( isset( \IPS\Request::i()->timescale[ $this->identifier ] ) and in_array( \IPS\Request::i()->timescale[ $this->identifier ], array( 'hourly', 'daily', 'weekly', 'monthly' ) ) )
/* 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 ] ) )
{
$this->searchTerm = \IPS\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( \IPS\Request::i()->timescale[ $this->identifier ] != 'hourly' OR ( \IPS\Request::i()->timescale[ $this->identifier ] == 'hourly' AND $this->enableHourly === TRUE ) )
{
@@ -190,13 +207,17 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
$this->addHeader( \IPS\Member::loggedIn()->language()->addToStack('date'), ( $this->timescale == 'none' OR $this->timescale == 'hourly' ) ? 'datetime' : 'date' );
}
if ( isset( \IPS\Request::i()->start[ $this->identifier ] ) and \IPS\Request::i()->start[ $this->identifier ] )
if ( isset( \IPS\Request::i()->start[ $this->identifier ] ) )
{
try
{
$originalStart = $this->start;
if ( is_numeric( \IPS\Request::i()->start[ $this->identifier ] ) )
if( !\IPS\Request::i()->start[ $this->identifier ] )
{
$this->start = 0;
}
elseif ( \is_numeric( \IPS\Request::i()->start[ $this->identifier ] ) )
{
$this->start = \IPS\DateTime::ts( \IPS\Request::i()->start[ $this->identifier ] );
}
@@ -223,11 +244,15 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
catch ( \Exception $e ) {}
}
if ( isset( \IPS\Request::i()->end[ $this->identifier ] ) and \IPS\Request::i()->end[ $this->identifier ] )
if ( isset( \IPS\Request::i()->end[ $this->identifier ] ) )
{
try
{
if ( is_numeric( \IPS\Request::i()->end[ $this->identifier ] ) )
if( !\IPS\Request::i()->end[ $this->identifier ] )
{
$this->end = \IPS\DateTime::create();
}
elseif ( \is_numeric( \IPS\Request::i()->end[ $this->identifier ] ) )
{
$this->end = \IPS\DateTime::ts( \IPS\Request::i()->end[ $this->identifier ] );
}
@@ -251,7 +276,7 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
$this->url = $url;
if ( \IPS\Member::loggedIn()->timezone and in_array( \IPS\Member::loggedIn()->timezone, \DateTimeZone::listIdentifiers() ) )
if ( \IPS\Member::loggedIn()->timezone and \in_array( \IPS\Member::loggedIn()->timezone, \DateTimeZone::listIdentifiers() ) )
{
try
{
@@ -280,6 +305,14 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
}
}
/**
* Compile Data for Output
*
* @param array $data The data
* @return void
*/
abstract public function compileForOutput(): void;
/**
* Get the chart output
*
@@ -302,7 +335,7 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
try
{
/* If we have filters, we can save them */
if( count( $this->availableFilters ) > 0 )
if( \count( $this->availableFilters ) > 0 )
{
/* We have an existing chart ID */
if( isset( \IPS\Request::i()->chartId ) AND \IPS\Request::i()->chartId != '_default' )
@@ -404,7 +437,7 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
{
$chartOutput = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->dynamicChart( $this, $output );
if( count( $this->availableFilters ) > 0 AND ( !\IPS\Request::i()->isAjax() OR ( \IPS\Request::i()->tab AND !\IPS\Request::i()->chartId ) ) )
if( \count( $this->availableFilters ) > 0 AND ( !\IPS\Request::i()->isAjax() OR ( \IPS\Request::i()->tab AND !\IPS\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' );
}
@@ -471,7 +504,7 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
{
$filters = $this->currentFilters;
if ( in_array( $filter, $filters ) )
if ( \in_array( $filter, $filters ) )
{
unset( $filters[ array_search( $filter, $filters ) ] );
}
@@ -502,7 +535,7 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
case 'hourly':
$data[ $date->format( 'Y-n-j-h-i-s' ) ] = array();
$date->add( new \DateInterval( 'P1H' ) );
$date->add( new \DateInterval( 'PT1H' ) );
break;
case 'daily':
@@ -530,4 +563,88 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
return $data;
}
/**
* 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
*/
public function download( ?array $rawHeaders = NULL, ?array $rawRows = NULL, ?string $fileName = NULL ): void
{
/* Compile the data */
$file = tempnam( \IPS\TEMP_DIRECTORY, 'IPS' );
$fh = fopen( $file, 'w' );
/* Set headers */
$headers = array();
if ( $rawHeaders === NULL )
{
$rawHeaders = $this->headers;
}
foreach( $rawHeaders AS $data )
{
$headers[] = $data['label'];
}
fputcsv( $fh, $headers );
/* Set Rows */
if ( $rawRows === NULL )
{
$rawRows = $this->rows;
}
foreach( $rawRows AS $row )
{
$i = 0;
$save = array();
foreach( $rawHeaders AS $data )
{
switch( $data['type'] )
{
/* Booleans convert to string 'true' or 'false' */
case 'bool':
$save[] = ( $row[$i] ) ? 'true' : 'false';
break;
/* Make dates human readable */
case 'date':
case 'datetime':
case 'timeofday':
$save[] = ( new \IPS\DateTime( $row[$i] ) )->fullYearLocaleDate();
break;
/* Anything else can just save as-is */
default:
$save[] = $row[$i];
break;
}
$i++;
}
fputcsv( $fh, $save );
}
fclose( $fh );
$csv = \file_get_contents( $file );
if ( $fileName )
{
$name = $fileName;
}
else if ( $this->title )
{
$name = $this->title;
}
else
{
$name = \IPS\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 );
}
}