Version 4.7.13

This commit is contained in:
Neo committed 2025-12-19 16:14:56 -08:00
1 parent fd22d99e69
commit 7124a02564
954 files changed
+29277 -59308

No files matched your search

@@ -104,6 +104,7 @@ class _settings extends \IPS\Dispatcher\Controller
}
else
{
\IPS\Session::i()->log( 'acplogs__achievements_rebuild' );
\IPS\core\Achievements\Rule::rebuildAllAchievements( $values['acp_rebuild_achievements_time'] ?: NULL );
\IPS\Settings::i()->changeValues( array('achievements_rebuilding' => 1) );
@@ -0,0 +1,48 @@
<?php
/**
* @brief ajax
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @since 09 May 2023
*/
namespace IPS\core\modules\admin\activitystats;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* ajax
*/
class _ajax extends \IPS\Dispatcher\Controller
{
/**
* Execute
*
* @return void
*/
public function execute()
{
parent::execute();
}
/**
* ...
*
* @return void
*/
protected function manage()
{
// This is the default method if no 'do' parameter is specified
}
// Create new methods with the same name as the 'do' parameter which should execute it
}
@@ -51,25 +51,7 @@ class _communityactivity extends \IPS\Dispatcher\Controller
*/
protected function manage()
{
$chart = new \IPS\Helpers\Chart\Callback(
\IPS\Http\Url::internal( 'app=core&module=activitystats&controller=communityactivity' ),
array( $this, 'getResults' ),
'',
array(
'isStacked' => TRUE,
'backgroundColor' => '#ffffff',
'colors' => array( '#10967e', '#ea7963' ),
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4
),
'AreaChart',
'weekly',
array( 'start' => \IPS\DateTime::create()->sub( new \DateInterval( 'P90D' ) ), 'end' => \IPS\DateTime::ts( time() ) )
);
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack('activity'), 'number', FALSE );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('member_activity_timeperiod');
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart' );
$chart = \IPS\core\Statistics\Chart::loadFromExtension( 'core', 'CommunityActivity' )->getChart( \IPS\Http\Url::internal( "app=core&module=activitystats&controller=communityactivity" ) );
if( \IPS\Request::i()->isAjax() )
{
@@ -82,107 +64,5 @@ class _communityactivity extends \IPS\Dispatcher\Controller
\IPS\Output::i()->output .= (string) $chart;
}
/**
* Fetch the results
*
* @param \IPS\Helpers\Chart\Callback $chart Chart object
* @return array
*/
public function getResults( $chart )
{
/* Get results */
$results = array();
if ( \IPS\Settings::i()->search_method == 'mysql' )
{
$results = array_replace_recursive( $results, $this->getSqlResults( 'core_search_index', 'index_date_updated', 'index_author', $chart ) );
}
else
{
foreach ( \IPS\Application::allExtensions( 'core', 'ContentRouter' ) as $contentRouter )
{
foreach ( $contentRouter->classes as $class )
{
if ( isset( $class::$databaseColumnMap['author'] ) )
{
$results = array_replace_recursive( $results, $this->getSqlResults(
$class::$databaseTable,
$class::$databasePrefix . ( isset( $class::$databaseColumnMap['updated'] ) ? $class::$databaseColumnMap['updated'] : $class::$databaseColumnMap['date'] ),
$class::$databasePrefix . $class::$databaseColumnMap['author'],
$chart
) );
}
}
}
}
$results = array_replace_recursive( $results, $this->getSqlResults( 'core_reputation_index', 'rep_date', 'member_id', $chart ) );
$results = array_replace_recursive( $results, $this->getSqlResults( 'core_follow', 'follow_added', 'follow_member_id', $chart ) );
/* Reformat now */
$finalResults = array();
foreach( $results as $date => $members )
{
$finalResults[ $date ] = array( 'time' => $date, \IPS\Member::loggedIn()->language()->get('activity') => \count( $members ) );
}
return $finalResults;
}
/**
* Get SQL query/results
*
* @note Consolidated to reduce duplicated code
* @param string $table Database table
* @param string $date Date column
* @param string $author Author column
* @param object $chart Chart
* @return array
*/
protected function getSqlResults( $table, $date, $author, $chart )
{
/* What's our SQL time? */
switch ( $chart->timescale )
{
case 'daily':
$timescale = '%Y-%c-%e';
break;
case 'weekly':
$timescale = '%x-%v';
break;
case 'monthly':
$timescale = '%Y-%c';
break;
}
$results = array();
$where = array( array( "{$date}>?", 0 ) );
$where[] = array( "{$author}>?", 0 );
if ( $chart->start )
{
$where[] = array( "{$date}>?", $chart->start->getTimestamp() );
}
if ( $chart->end )
{
$where[] = array( "{$date}<?", $chart->end->getTimestamp() );
}
/* First we need to get search index activity */
$fromUnixTime = "FROM_UNIXTIME( IFNULL( {$date}, 0 ) )";
if ( !$chart->timezoneError and \IPS\Member::loggedIn()->timezone and \in_array( \IPS\Member::loggedIn()->timezone, \IPS\DateTime::getTimezoneIdentifiers() ) )
{
$fromUnixTime = "CONVERT_TZ( {$fromUnixTime}, @@session.time_zone, '" . \IPS\Db::i()->escape_string( \IPS\Member::loggedIn()->timezone ) . "' )";
}
$stmt = \IPS\Db::i()->select( "DATE_FORMAT( {$fromUnixTime}, '{$timescale}' ) AS time, {$author}", $table, $where, 'time ASC', NULL, array( $author, 'time' ) );
foreach( $stmt as $row )
{
$results[ $row['time'] ][ $row[ $author ] ] = 1;
}
return $results;
}
}
@@ -46,34 +46,7 @@ class _deletedcontent extends \IPS\Dispatcher\Controller
*/
protected function manage()
{
$chart = new \IPS\Helpers\Chart\Database( \IPS\Http\Url::internal( 'app=core&module=activitystats&controller=deletedcontent' ), 'core_deletion_log', 'dellog_deleted_date', '', array(
'isStacked' => TRUE,
'backgroundColor' => '#ffffff',
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4
), 'LineChart', 'monthly', array( 'start' => 0, 'end' => 0 ), array( 'dellog_content_class', 'dellog_deleted_date' ), 'deletedcontent' );
$chart->groupBy = 'dellog_content_class';
$types = \IPS\Db::i()->select( 'DISTINCT(dellog_content_class)', 'core_deletion_log' );
foreach( $types as $class )
{
$lang = $class::$title;
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack( $lang ), 'number', 'COUNT(*)', TRUE, $class );
}
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('stats_deletedcontent_title');
$chart->availableTypes = array( 'LineChart', 'AreaChart', 'ColumnChart', 'BarChart' );
$chart->tableParsers = array(
'dellog_deleted_date' => function( $val )
{
return (string) \IPS\DateTime::ts( $val );
}
);
$chart = \IPS\core\Statistics\Chart::loadFromExtension( 'core', 'DeletedContent' )->getChart( \IPS\Http\Url::internal( "app=core&module=activitystats&controller=deletedcontent" ) );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_activitystats_deletedcontent');
\IPS\Output::i()->output = (string) $chart;
}
@@ -59,68 +59,7 @@ class _emailstats extends \IPS\Dispatcher\Controller
{
$activeTab = $this->_getActiveTab();
/* Determine minimum date */
$minimumDate = NULL;
if( \IPS\Settings::i()->prune_log_emailstats > 0 )
{
$minimumDate = \IPS\DateTime::create()->sub( new \DateInterval( 'P' . \IPS\Settings::i()->prune_log_emailstats . 'D' ) );
}
/* We can't retrieve any stats prior to the new tracking being implemented */
try
{
$oldestLog = \IPS\Db::i()->select( 'MIN(time)', 'core_statistics', array( 'type=?', 'emails_sent' ) )->first();
if( !$minimumDate OR $oldestLog < $minimumDate->getTimestamp() )
{
$minimumDate = \IPS\DateTime::ts( $oldestLog );
}
}
catch( \UnderflowException $e )
{
/* We have nothing tracked, set minimum date to today */
$minimumDate = \IPS\DateTime::create();
}
$startDate = \IPS\DateTime::ts( time() - ( 60 * 60 * 24 * 30 ) );
/* If our start date is older than our minimum date, use that as the start date instead */
if ( $startDate->getTimestamp() < $minimumDate->getTimestamp() )
{
$startDate = $minimumDate;
}
$chart = new \IPS\Helpers\Chart\Callback(
\IPS\Http\Url::internal( 'app=core&module=activitystats&controller=emailstats&tab=' . $activeTab ),
array( $this, 'getResults' ),
'',
array(
'isStacked' => TRUE,
'backgroundColor' => '#ffffff',
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4,
'height' => 450
),
'LineChart',
'daily',
array( 'start' => $startDate, 'end' => \IPS\DateTime::create() ),
'',
$minimumDate
);
$chart->availableTypes = array( 'LineChart', 'ColumnChart', 'BarChart' );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('stats_emailstats_' . $activeTab);
/* Force the notice that the chart is displayed in the server time zone */
$chart->timezoneError = TRUE;
$chart->hideTimezoneLink = TRUE;
foreach( $this->_getEmailTypes() as $series )
{
$chart->addSeries( $series, 'number' );
}
$chart = \IPS\core\Statistics\Chart::loadFromExtension( 'core', ( $activeTab === 'emails' ) ? 'Emails' : 'EmailClicks' )->getChart( \IPS\Http\Url::internal( "app=core&module=activitystats&controller=emailstats&tab={$activeTab}" ) );
if ( \IPS\Request::i()->isAjax() )
{
@@ -133,85 +72,6 @@ class _emailstats extends \IPS\Dispatcher\Controller
}
}
/**
* Fetch the results
*
* @param \IPS\Helpers\Chart\Callback $chart Chart object
* @return array
*/
public function getResults( $chart )
{
$activeTab = $this->_getActiveTab();
$where = array( array( "time>?", 0 ) );
switch( $activeTab )
{
case 'emails':
$where[] = array( 'type=?', 'emails_sent' );
break;
case 'clicks':
$where[] = array( 'type=?', 'email_clicks' );
break;
}
if ( $chart->start )
{
$where[] = array( "value_4>=?", $chart->start->format('Y-m-d') );
}
if ( $chart->end )
{
$where[] = array( "value_4<=?", $chart->end->format('Y-m-d') );
}
$results = array();
foreach( \IPS\Db::i()->select( '*', 'core_statistics', $where, 'value_4 ASC' ) as $row )
{
/* We need to use month/days NOT prefixed with '0' - i.e. 2019-2-12 instead of 2019-02-12 - to match the chart helper */
$_date = new \IPS\DateTime( $row['value_4'] );
switch( $chart->timescale )
{
case 'daily':
$_date = $_date->format( 'Y-n-j' );
break;
case 'weekly':
$_date = $_date->format( 'o-W' );
break;
case 'monthly':
$_date = $_date->format( 'Y-n' );
break;
}
if( !isset( $results[ $_date ] ) )
{
$results[ $_date ] = array( 'time' => $_date );
foreach( $this->_getEmailTypes() as $series )
{
$results[ $_date ][ $series ] = 0;
}
}
$lang = $row['extra_data'];
try
{
$lang = \IPS\Member::loggedIn()->language()->get( 'emailstats__' . $row['extra_data'] );
}
catch( \UnderflowException $e ){}
$results[ $_date ][ $lang ] += (int) $row['value_1'];
}
return $results;
}
/**
* Get the active tab
*
@@ -219,7 +79,8 @@ class _emailstats extends \IPS\Dispatcher\Controller
*/
protected function _getActiveTab()
{
return ( isset( \IPS\Request::i()->tab ) and array_key_exists( \IPS\Request::i()->tab, $this->_getAvailableTabs() ) ) ? \IPS\Request::i()->tab : 'emails';
\IPS\Request::i()->tab ??= 'emails';
return ( array_key_exists( \IPS\Request::i()->tab, $this->_getAvailableTabs() ) ) ? \IPS\Request::i()->tab : 'emails';
}
/**
@@ -234,37 +95,4 @@ class _emailstats extends \IPS\Dispatcher\Controller
'clicks' => 'stats_emailstats_clicks',
);
}
/**
* @brief Cached email types
*/
protected $_emailTypes = NULL;
/**
* Get all possible email types logged
*
* @return array
*/
protected function _getEmailTypes()
{
if( $this->_emailTypes === NULL )
{
$this->_emailTypes = array();
foreach( \IPS\Db::i()->select( 'extra_data', 'core_statistics', array( 'type=?', 'emails_sent' ), NULL, NULL, NULL, NULL, \IPS\Db::SELECT_DISTINCT ) as $series )
{
$lang = $series;
try
{
$lang = \IPS\Member::loggedIn()->language()->get( 'emailstats__' . $series );
}
catch( \UnderflowException $e ){}
$this->_emailTypes[] = $lang;
}
}
return array_unique( $this->_emailTypes );
}
}
@@ -58,134 +58,100 @@ class _keywords extends \IPS\Dispatcher\Controller
'link' => \IPS\Http\Url::internal( 'app=core&module=activitystats&controller=keywords&do=settings' ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('settings') )
);
/* Determine minimum date */
$minimumDate = NULL;
if( \IPS\Settings::i()->stats_keywords_prune )
{
$minimumDate = \IPS\DateTime::create()->sub( new \DateInterval( 'P' . \IPS\Settings::i()->stats_keywords_prune . 'D' ) );
}
/* Draw a chart */
$options = json_decode( \IPS\Settings::i()->stats_keywords, true );
if( !\is_array( $options ) )
{
$options = array();
}
$chart = new \IPS\Helpers\Chart\Database(
\IPS\Http\Url::internal( 'app=core&module=activitystats&controller=keywords' ),
'core_statistics',
'time',
'',
array(
'isStacked' => TRUE,
'backgroundColor' => '#ffffff',
'colors' => array( '#10967e', '#ea7963', '#de6470', '#6b9dde', '#b09be4', '#eec766', '#9fc973', '#e291bf', '#55c1a6', '#5fb9da' ),
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4
),
'LineChart',
'daily',
array( 'start' => \IPS\DateTime::create()->sub( new \DateInterval( 'P90D' ) ), 'end' => \IPS\DateTime::ts( time() ) ),
array(),
'',
$minimumDate
);
$chart->where = array( array( 'type=?', 'keyword' ) );
$chart->groupBy = 'value_4';
if ( \is_array( $options ) )
{
foreach( $options as $k => $v )
{
$chart->addSeries( $v, 'number', 'COUNT(*)' );
}
}
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('keyword_usage_chart');
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart' );
\IPS\Output::i()->output = (string) $chart;
if( \IPS\Request::i()->noheader AND \IPS\Request::i()->isAjax() )
{
return;
}
/* Create the table */
$table = new \IPS\Helpers\Table\Db( 'core_statistics', \IPS\Http\Url::internal( 'app=core&module=activitystats&controller=keywords' ), array( array( 'type=?', 'keyword' ) ) );
$table->langPrefix = 'keywordstats_';
$table->quickSearch = 'value_4';
/* Columns we need */
$table->include = array( 'value_4', 'extra_data', 'author', 'time' );
$table->mainColumn = 'value_4';
$table->noSort = array( 'extra_data' );
$table->sortBy = $table->sortBy ?: 'time';
$table->sortDirection = $table->sortDirection ?: 'desc';
/* Custom parsers */
$table->parsers = array(
'time' => function( $val, $row )
{
return \IPS\DateTime::ts( $val )->localeDate();
},
'author' => function( $val, $row )
{
$data = json_decode( $row['extra_data'], TRUE );
try
{
$class = $data['class'];
/* Check that the class exists */
if( !class_exists( $class ) )
{
throw new \InvalidArgumentException;
}
$item = $class::load( $data['id'] );
return $item->author()->link();
}
catch( \Exception $e )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'unknown' );
}
},
'extra_data' => function( $val, $row )
{
$data = json_decode( $val, TRUE );
try
{
$class = $data['class'];
/* Check that the class exists */
if( !class_exists( $class ) )
{
throw new \InvalidArgumentException;
}
$item = $class::load( $data['id'] );
return \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $item->url(), TRUE, ( $item instanceof \IPS\Content\Comment ) ? $item->item()->mapped('title') : $item->mapped('title'), TRUE );
}
catch( \Exception $e )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'content_deleted' );
}
},
$tabs = array(
'time' => 'keywords_usage_over_time',
'list' => 'keywords_usage_list',
);
/* Display */
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_activitystats_keywords');
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'global', 'core' )->block( 'title', (string) $table, TRUE, 'ipsPad ipsSpacer_top' );
\IPS\Request::i()->tab ??= 'time';
$activeTab = ( array_key_exists( \IPS\Request::i()->tab, $tabs ) ) ? \IPS\Request::i()->tab : 'time';
if ( $activeTab === 'time' )
{
$output = \IPS\core\Statistics\Chart::loadFromExtension( 'core', 'Keywords' )->getChart( \IPS\Http\Url::internal( "app=core&module=activitystats&controller=keywords&tab=time" ) );
}
else
{
/* Create the table */
$table = new \IPS\Helpers\Table\Db( 'core_statistics', \IPS\Http\Url::internal( 'app=core&module=activitystats&controller=keywords&tab=list' ), array( array( 'type=?', 'keyword' ) ) );
$table->langPrefix = 'keywordstats_';
$table->quickSearch = 'value_4';
/* Columns we need */
$table->include = array( 'value_4', 'extra_data', 'author', 'time' );
$table->mainColumn = 'value_4';
$table->noSort = array( 'extra_data' );
$table->sortBy = $table->sortBy ?: 'time';
$table->sortDirection = $table->sortDirection ?: 'desc';
/* Custom parsers */
$table->parsers = array(
'time' => function( $val, $row )
{
return \IPS\DateTime::ts( $val )->localeDate();
},
'author' => function( $val, $row )
{
$data = json_decode( $row['extra_data'], TRUE );
try
{
$class = $data['class'];
/* Check that the class exists */
if( !class_exists( $class ) )
{
throw new \InvalidArgumentException;
}
$item = $class::load( $data['id'] );
return $item->author()->link();
}
catch( \Exception $e )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'unknown' );
}
},
'extra_data' => function( $val, $row )
{
$data = json_decode( $val, TRUE );
try
{
$class = $data['class'];
/* Check that the class exists */
if( !class_exists( $class ) )
{
throw new \InvalidArgumentException;
}
$item = $class::load( $data['id'] );
return \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $item->url(), TRUE, ( $item instanceof \IPS\Content\Comment ) ? $item->item()->mapped('title') : $item->mapped('title'), TRUE );
}
catch( \Exception $e )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'content_deleted' );
}
},
);
/* Display */
$output = \IPS\Theme::i()->getTemplate( 'global', 'core' )->block( 'title', (string) $table, TRUE, 'ipsPad ipsSpacer_top' );
}
if ( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->output = (string) $output;
}
else
{
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_activitystats_keywords');
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global', 'core' )->tabs( $tabs, $activeTab, (string) $output, \IPS\Http\Url::internal( "app=core&module=activitystats&controller=keywords" ), 'tab', '', 'ipsPad' );
}
}
/**
@@ -211,4 +177,28 @@ class _keywords extends \IPS\Dispatcher\Controller
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('settings');
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('global')->block( 'settings', $form, FALSE );
}
/**
* Rebuild
*
* @return void
*/
protected function rebuild()
{
if ( !\IPS\Settings::i()->stats_keywords )
{
\IPS\Output::i()->error( 'no_keywords_to_rebuild', '2C433/1', 403, '' );
}
\IPS\Session::i()->csrfCheck();
\IPS\Db::i()->delete( 'core_statistics', array( 'type=?', 'keyword' ) );
foreach( \IPS\Content::routedClasses() AS $class )
{
\IPS\Task::queue( 'core', 'RebuildKeywords', array( 'class' => $class ) );
}
\IPS\Session::i()->log( 'acplog__statskeywords_rebuilt' );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=activitystats&controller=keywords' ), 'saved' );
}
}
@@ -51,7 +51,8 @@ class _overview extends \IPS\core\modules\admin\stats\overview
if( isset( \IPS\Request::i()->cookie['overviewExcludedApps'] ) )
{
try {
try
{
$excludedApps = json_decode( \IPS\Request::i()->cookie['overviewExcludedApps'] );
if( !\is_array( $excludedApps ) )
@@ -57,45 +57,12 @@ class _reactions extends \IPS\Dispatcher\Controller
'givers' => 'stats_reactions_top_givers',
'getters' => 'stats_reactions_top_receivers',
);
$activeTab = ( isset( \IPS\Request::i()->tab ) and array_key_exists( \IPS\Request::i()->tab, $tabs ) ) ? \IPS\Request::i()->tab : 'type';
\IPS\Request::i()->tab ??= 'type';
$activeTab = ( array_key_exists( \IPS\Request::i()->tab, $tabs ) ) ? \IPS\Request::i()->tab : 'type';
if ( $activeTab === 'type' OR $activeTab === 'app' )
{
$chart = new \IPS\Helpers\Chart\Database( \IPS\Http\Url::internal( "app=core&module=activitystats&controller=reactions&tab=" . $activeTab ), 'core_reputation_index', 'rep_date', '', array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'colors' => array( '#10967e', '#ea7963', '#de6470', '#6b9dde', '#b09be4', '#eec766', '#9fc973', '#e291bf', '#55c1a6', '#5fb9da' ),
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4
),
'PieChart',
'monthly',
array( 'start' => 0, 'end' => 0 ),
array(),
$activeTab
);
if ( $activeTab === 'type' )
{
$chart->groupBy = 'reaction';
foreach( \IPS\Content\Reaction::roots() as $reaction )
{
$chart->addSeries( $reaction->_title, 'number', 'COUNT(*)', TRUE, $reaction->id );
}
}
else
{
$chart->groupBy = 'app';
foreach( \IPS\Application::roots() as $app )
{
$chart->addSeries( $app->_title, 'number', 'COUNT(*)', TRUE, $app->directory );
}
}
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart', 'PieChart' );
$chart = \IPS\core\Statistics\Chart::loadFromExtension( 'core', ( $activeTab === 'type' ) ? 'Reactions' : 'ReactionsApp' )->getChart( \IPS\Http\Url::internal( "app=core&module=activitystats&controller=reactions&tab=" . $activeTab ) );
}
else
{
@@ -41,19 +41,7 @@ class _reports extends \IPS\Dispatcher\Controller
*/
protected function manage()
{
$chart = new \IPS\Helpers\Chart\Database( \IPS\Http\Url::internal( 'app=core&module=activitystats&controller=reports' ), 'core_rc_reports', 'date_reported', '', array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'colors' => array( '#10967e' ),
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4
) );
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack('stats_reports'), 'number', 'COUNT(*)', FALSE );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('stats_reports_title');
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart' );
$chart = \IPS\core\Statistics\Chart::loadFromExtension( 'core', 'Reports' )->getChart( \IPS\Http\Url::internal( "app=core&module=activitystats&controller=reports" ) );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_activitystats_reports');
\IPS\Output::i()->output = (string) $chart;
}
@@ -22,11 +22,6 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
*/
class _search extends \IPS\Dispatcher\Controller
{
/**
* @brief Default limit to number of graphed results
*/
protected $defaultLimit = 25;
/**
* @brief Has been CSRF-protected
*/
@@ -69,53 +64,8 @@ class _search extends \IPS\Dispatcher\Controller
'title' => 'searchstats_log',
'link' => \IPS\Http\Url::internal( 'app=core&module=activitystats&controller=search&do=log' ),
);
/* Determine minimum date */
$minimumDate = NULL;
if( \IPS\Settings::i()->stats_search_prune )
{
$minimumDate = \IPS\DateTime::create()->sub( new \DateInterval( 'P' . \IPS\Settings::i()->stats_search_prune . 'D' ) );
}
$chart = new \IPS\Helpers\Chart\Database(
\IPS\Http\Url::internal( 'app=core&module=activitystats&controller=search' ),
'core_statistics',
'time',
'',
array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'colors' => array( '#10967e', '#ea7963', '#de6470', '#6b9dde', '#b09be4', '#eec766', '#9fc973', '#e291bf', '#55c1a6', '#5fb9da' ),
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4,
'limitSearch' => 'stats_search_term_menu',
),
'LineChart',
'daily',
array( 'start' => \IPS\DateTime::create()->sub( new \DateInterval( 'P90D' ) ), 'end' => \IPS\DateTime::ts( time() ) ),
array(),
'',
$minimumDate
);
$chart->where = array( array( 'type=?', 'search' ) );
$chart->groupBy = 'value_4';
$terms = [];
foreach( $this->getTerms( $chart->searchTerm ) as $v )
{
$terms[] = $v;
$chart->addSeries( $v, 'number', 'COUNT(*)', FALSE );
}
if ( \count( $terms ) )
{
$chart->where[] = [ \IPS\Db::i()->in( 'value_4', $terms ) ];
}
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('search_stats_chart');
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart' );
$chart = \IPS\core\Statistics\Chart::loadFromExtension( 'core', 'Search' )->getChart( \IPS\Http\Url::internal( "app=core&module=activitystats&controller=search" ) );
\IPS\Output::i()->output = (string) $chart;
@@ -128,39 +78,6 @@ class _search extends \IPS\Dispatcher\Controller
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_activitystats_search');
}
/**
* @brief Cached top search terms
*/
protected $_topSearchTerms = array();
/**
* Get the top search terms
*
* @param string|null $term Term we searched for
* @return array
*/
public function getTerms( $term=NULL )
{
if( !isset( $this->_topSearchTerms[ $term ] ) )
{
$this->_topSearchTerms[ $term ] = array();
$where = array( array( 'type=?', 'search' ) );
if( $term !== NULL )
{
$where[] = \IPS\Db::i()->like( 'value_4', $term, TRUE, TRUE, TRUE );
}
foreach( \IPS\Db::i()->select( 'SQL_BIG_RESULT value_4, COUNT(*) as total', 'core_statistics', $where, 'total DESC', $this->defaultLimit, 'value_4' ) as $searchedValue )
{
$this->_topSearchTerms[ $term ][] = $searchedValue['value_4'];
}
}
return $this->_topSearchTerms[ $term ];
}
/**
* Prune Settings
*
@@ -50,39 +50,7 @@ class _tags extends \IPS\Dispatcher\Controller
*/
protected function manage()
{
$chart = new \IPS\Helpers\Chart\Database( \IPS\Http\Url::internal( "app=core&module=activitystats&controller=tags" ), 'core_tags', 'tag_added', '', array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4
),
'AreaChart',
'daily',
array( 'start' => \IPS\DateTime::create()->sub( new \DateInterval( 'P1M' ) ), 'end' => 0 )
);
$chart->groupBy = 'tag_text';
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart', 'PieChart' );
$where = $chart->where;
$where[] = array( "tag_added>?", 0 );
if ( $chart->start )
{
$where[] = array( "tag_added>?", $chart->start->getTimestamp() );
}
if ( $chart->end )
{
$where[] = array( "tag_added<?", $chart->end->getTimestamp() );
}
/* Only get visible tags */
$where[] = array( 'tag_aai_lookup IN(?)', \IPS\Db::i()->select( 'tag_perm_aai_lookup', 'core_tags_perms', [ 'tag_perm_visible=1' ] ) );
foreach( \IPS\Db::i()->select( 'tag_text', 'core_tags', $where, NULL, NULL, array( 'tag_text' ) ) as $tag )
{
$chart->addSeries( $tag, 'number', 'COUNT(*)', TRUE, $tag );
}
$chart = \IPS\core\Statistics\Chart::loadFromExtension( 'core', 'Tags' )->getChart( \IPS\Http\Url::internal( "app=core&module=activitystats&controller=tags" ) );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_activitystats_tags');
\IPS\Output::i()->output = (string) $chart;
}
@@ -36,6 +36,8 @@ class _api extends \IPS\Dispatcher\Controller
*/
public function __call( $method, $args )
{
\IPS\Output::i()->responsive = FALSE;
/* Check htaccess is correct */
if ( \IPS\Settings::i()->use_friendly_urls and \IPS\Settings::i()->htaccess_mod_rewrite OR \IPS\Request::i()->fromZapier )
{
@@ -60,18 +60,20 @@ class _applications extends \IPS\Node\Controller
{
if( \IPS\IPS::canManageResources() )
{
if ( \IPS\IPS::checkThirdParty() )
foreach( \IPS\Application::applications() as $app )
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('forms')->blurb( 'applications_blurb' );
}
else
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('forms')->blurb('applications_blurb_no_custom');
if( $app->marketplace_id )
{
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate('marketplace')->removalBanner(FALSE);
break;
}
}
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate('forms')->blurb( 'applications_blurb' );
}
else
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('forms')->blurb( 'applications_blurb_no_upload' );
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate('forms')->blurb( 'applications_blurb_no_upload' );
}
}
parent::manage();
@@ -154,7 +156,7 @@ class _applications extends \IPS\Node\Controller
function( $key, $root=FALSE ) use ( $uninstalled, $baseUrl )
{
$buttons = array();
if( \IPS\IPS::canManageResources() AND \IPS\IPS::checkThirdParty() )
if( \IPS\IPS::canManageResources() )
{
$buttons = array(
'add' => array(
@@ -212,7 +214,7 @@ class _applications extends \IPS\Node\Controller
foreach ( $legacyApps as $k => $app )
{
$buttons = array();
if( \IPS\IPS::canManageResources() AND \IPS\IPS::checkThirdParty() )
if( \IPS\IPS::canManageResources() )
{
$buttons['upgrade'] = array(
'icon' => 'upload',
@@ -241,7 +243,7 @@ class _applications extends \IPS\Node\Controller
function( $key, $root=FALSE ) use ( $legacyApps, $baseUrl )
{
$buttons = array();
if( \IPS\IPS::canManageResources() AND \IPS\IPS::checkThirdParty() )
if( \IPS\IPS::canManageResources() )
{
$buttons['upgrade'] = array(
'icon' => 'upload',
@@ -610,11 +612,11 @@ class _applications extends \IPS\Node\Controller
{
foreach ( \IPS\Application::applications() as $application )
{
/* Skip building Marketplace applications */
if( $application->marketplace_id )
{
continue;
}
/* Skip building defined apps */
if( \in_array( $application->directory, \IPS\DEV_SKIP_BUILD_APPS ) )
{
continue;
}
if ( $values['developer_build_type'] === 'new' )
{
@@ -727,12 +729,6 @@ class _applications extends \IPS\Node\Controller
{
\IPS\Output::i()->error( 'not_in_dev', '2C133/10', 403, '' );
}
/* Don't allow downloads of Marketplace resources */
if( $application->marketplace_id )
{
// \IPS\Output::i()->error( 'application_cannot_build_marketplace', '2C133/Y', 403, '' );
}
$form = $this->_buildForm( $application->directory, TRUE );
if ( $values = $form->values() )
@@ -778,6 +774,19 @@ class _applications extends \IPS\Node\Controller
\IPS\Output::i()->output = $form;
}
/**
* Toggle Enabled/Disable
*
* @return void
*/
protected function enableToggle()
{
parent::enableToggle();
/* update the essential cookie name list */
unset( \IPS\Data\Store::i()->essentialCookieNames );
}
/**
* Upgrade an application that is currently installed. After importing a PHAR the user is redirected to this method.
*
@@ -1041,11 +1050,9 @@ class _applications extends \IPS\Node\Controller
/* Return null to indicate we are done */
if( $laststep === NULL )
{
if( \IPS\Request::i()->marketplace AND !\IPS\Application::load( $data['key'] )->marketplace_id )
{
\IPS\Application::load( $data['key'] )->marketplace_id = (int) \IPS\Request::i()->marketplace;
\IPS\Application::load( $data['key'] )->save();
}
\IPS\Application::load( $data['key'] )->marketplace_id = \IPS\Request::i()->marketplace ? (int) \IPS\Request::i()->marketplace : NULL;
\IPS\Application::load( $data['key'] )->save();
\IPS\Session::i()->log( 'acplog__application_updated', array( \IPS\Application::load( $data['key'] )->titleForLog() => FALSE, \IPS\Application::load( $data['key'] )->version => TRUE ) );
return NULL;
@@ -1300,11 +1307,8 @@ class _applications extends \IPS\Node\Controller
/* Return null to indicate we are done */
if( $laststep === NULL )
{
if( \IPS\Request::i()->marketplace )
{
\IPS\Application::load( $data['key'] )->marketplace_id = (int) \IPS\Request::i()->marketplace;
\IPS\Application::load( $data['key'] )->save();
}
\IPS\Application::load( $data['key'] )->marketplace_id = \IPS\Request::i()->marketplace ? (int) \IPS\Request::i()->marketplace : NULL;
\IPS\Application::load( $data['key'] )->save();
\IPS\Session::i()->log( 'acplog__application_installed', array( "__app_" . $data['key'] => TRUE ) );
@@ -1563,11 +1567,6 @@ class _applications extends \IPS\Node\Controller
{
\IPS\Output::i()->error( 'no_phar_extension', '1C133/P', 403, '' );
}
if ( !\IPS\IPS::checkThirdParty() )
{
\IPS\Output::i()->error( 'cic_3rdparty_unavailable', '2C133/Z', 403, '' );
}
$_type = 'install';
@@ -1619,16 +1618,14 @@ class _applications extends \IPS\Node\Controller
\IPS\Output::i()->error( \IPS\Member::loggedIn()->language()->addToStack( "app_upload_designersmode", FALSE, array( 'sprintf' => \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=designersmode' ) ) ), '2C133/O', 403, '' );
}
/* Check Marketplace Status? */
if( $app->marketplace_id )
{
// \IPS\Output::i()->error( 'app_upload_marketplace_only', '2C133/X', 403, '' );
}
$_type = 'upgrade';
}
$form = new \IPS\Helpers\Form( 'form', 'install' );
if( $_type == 'upgrade' AND $app->marketplace_id )
{
$form->addMessage( 'marketplace_resource_manual_upgrade', 'ipsMessage ipsMessage_info' );
}
$form->addMessage('applications_manual_install_warning');
$form->add( new \IPS\Helpers\Form\Upload( 'application_file', NULL, TRUE, array( 'allowedFileTypes' => array( 'tar' ), 'temporary' => TRUE ) ) );
@@ -130,24 +130,20 @@ class _developer extends \IPS\Dispatcher\Controller
/* Display */
if ( $activeTabContents )
{
/* Add the build and download button */
if( !$this->application->marketplace_id )
{
\IPS\Output::i()->jsFiles = array_merge(\IPS\Output::i()->jsFiles, \IPS\Output::i()->js('admin_system.js', 'core', 'admin'));
\IPS\Output::i()->sidebar['actions']['build'] = array(
'icon' => 'download',
'title' => 'download',
'link' => \IPS\Http\Url::internal("app=core&module=applications&controller=applications&appKey={$this->application->directory}&do=download"),
'data' => array(
'controller' => 'system.buildApp',
'downloadURL' => \IPS\Http\Url::internal("app=core&module=applications&controller=applications&appKey={$this->application->directory}&do=download&type=download"),
'buildURL' => \IPS\Http\Url::internal("app=core&module=applications&controller=applications&appKey={$this->application->directory}&do=download&type=build"),
)
);
}
\IPS\Output::i()->jsFiles = array_merge(\IPS\Output::i()->jsFiles, \IPS\Output::i()->js('admin_system.js', 'core', 'admin'));
\IPS\Output::i()->sidebar['actions']['build'] = array(
'icon' => 'download',
'title' => 'download',
'link' => \IPS\Http\Url::internal("app=core&module=applications&controller=applications&appKey={$this->application->directory}&do=download"),
'data' => array(
'controller' => 'system.buildApp',
'downloadURL' => \IPS\Http\Url::internal("app=core&module=applications&controller=applications&appKey={$this->application->directory}&do=download&type=download"),
'buildURL' => \IPS\Http\Url::internal("app=core&module=applications&controller=applications&appKey={$this->application->directory}&do=download&type=build"),
)
);
\IPS\Output::i()->title = $this->application->_title;
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->tabs( $tabs, $this->activeTab, $activeTabContents, \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}" ) );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->tabs( $tabs, $this->activeTab, $activeTabContents,\IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}" ) );
}
}
@@ -2859,9 +2855,12 @@ class _developer extends \IPS\Dispatcher\Controller
protected function removeExtension()
{
\IPS\Session::i()->csrfCheck();
unlink( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/extensions/" . \IPS\Request::i()->ext . "/" . \IPS\Request::i()->file . ".php" );
if( is_file( \IPS\ROOT_PATH."/applications/{$this->application->directory}/extensions/".\IPS\Request::i()->ext.'/'.\IPS\Request::i()->file.'.php'))
{
unlink( \IPS\ROOT_PATH."/applications/{$this->application->directory}/extensions/".\IPS\Request::i()->ext.'/'.\IPS\Request::i()->file.'.php' );
}
$this->_writeJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/extensions.json", $this->application->buildExtensionsJson() );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=extensions" ), 'deleted' );
}
@@ -74,14 +74,16 @@ class _plugins extends \IPS\Node\Controller
if ( !\IPS\Request::i()->isAjax() AND \IPS\IPS::canManageResources() )
{
if ( \IPS\IPS::checkThirdParty() )
foreach( \IPS\Plugin::plugins() as $app )
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('forms')->blurb( 'plugins_blurb' );
}
else
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('forms')->blurb('plugins_blurb_no_custom');
if( $app->marketplace_id )
{
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate('marketplace')->removalBanner(FALSE);
break;
}
}
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate('forms')->blurb( 'plugins_blurb' );
}
parent::manage();
@@ -202,11 +204,6 @@ class _plugins extends \IPS\Node\Controller
{
\IPS\Output::i()->error( 'plugin_file_not_write', '4C145/N', 403, '' );
}
if ( \IPS\CIC AND ! \IPS\IPS::checkThirdParty() )
{
\IPS\Output::i()->error( 'cic_3rdparty_unavailable', '2C145/S', 403, '' );
}
/* Build form */
$form = new \IPS\Helpers\Form( NULL, 'install' );
@@ -214,9 +211,9 @@ class _plugins extends \IPS\Node\Controller
{
$form->hiddenValues['id'] = \IPS\Request::i()->id;
if( \IPS\Plugin::load( \IPS\Request::i()->id )->marketplace_id )
if( \IPS\Plugin::load( (int) \IPS\Request::i()->id,'plugin_id')->marketplace_id )
{
\IPS\Output::i()->error( 'app_upload_marketplace_only', '2C145/P', 403, '' );
$form->addMessage( 'marketplace_resource_manual_upgrade', 'ipsMessage ipsMessage_info' );
}
}
$form->addMessage('plugins_manual_install_warning');
@@ -1191,10 +1188,7 @@ class _plugins extends \IPS\Node\Controller
\IPS\Session::i()->log( 'acplog__plugin_installed', array( $plugin->name => FALSE ) );
/* Set Marketplace ID. */
if( \IPS\Request::i()->marketplace )
{
$plugin->marketplace_id = (int) \IPS\Request::i()->marketplace;
}
$plugin->marketplace_id = \IPS\Request::i()->marketplace ? (int) \IPS\Request::i()->marketplace : NULL;
/* Re-enable the plugin */
$plugin->enabled = TRUE;
@@ -1340,16 +1334,13 @@ class _plugins extends \IPS\Node\Controller
if ( $activeTabContents )
{
/* Add Download Button */
if( !$plugin->marketplace_id )
{
\IPS\Output::i()->sidebar['actions'] = array(
'download' => array(
'icon' => 'download',
'title' => 'download',
'link' => \IPS\Http\Url::internal("app=core&module=applications&controller=plugins&do=download&id={$plugin->id}")->csrf(),
)
);
}
\IPS\Output::i()->sidebar['actions'] = array(
'download' => array(
'icon' => 'download',
'title' => 'download',
'link' => \IPS\Http\Url::internal("app=core&module=applications&controller=plugins&do=download&id={$plugin->id}")->csrf(),
)
);
\IPS\Output::i()->title = $plugin->name;
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->tabs( $tabs, $activeTab, $activeTabContents, \IPS\Http\Url::internal( "app=core&module=applications&controller=plugins&do=developer&id={$plugin->id}" ) );
@@ -1874,12 +1865,6 @@ class _plugins extends \IPS\Node\Controller
\IPS\Output::i()->error( 'node_error', '2C145/B', 404, '' );
}
/* Don't allow downloads of Marketplace resources */
if( $plugin->marketplace_id )
{
\IPS\Output::i()->error( 'plugin_cannot_build_marketplace', '2C145/Q', 403, '' );
}
/* Init */
$xml = \IPS\Xml\SimpleXML::create('plugin');
$xml->addAttribute( 'name', $plugin->name );
@@ -231,13 +231,34 @@ class _clubs extends \IPS\Dispatcher\Controller
/* Settings */
if ( $activeTab === 'settings' )
{
if ( $form = $club->form( TRUE ) )
$form = $club->form( TRUE );
if( $values = $form->values() )
{
$activeTabContents = (string) $form;
$club->skipCloneDuplication = TRUE;
$old = clone $club;
$club->processForm( $values, TRUE, FALSE, NULL );
$changes = $club::renewalChanges( $old, $club );
if ( !empty( $changes ) )
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->decision( 'product_change_blurb', array(
'product_change_blurb_existing' => \IPS\Http\Url::internal( "app=core&module=clubs&controller=clubs&do=updateExisting&id={$club->id}" )->setQueryString( 'changes', json_encode( $changes ) )->csrf(),
'product_change_blurb_new' => \IPS\Http\Url::internal( "app=core&module=clubs&controller=clubs" ),
) );
return;
}
else
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal("app=core&module=clubs&controller=clubs"), "saved" );
}
}
else
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal("app=core&module=clubs&controller=clubs"), "saved" );
$activeTabContents = (string) $form;
}
}
@@ -641,4 +662,29 @@ class _clubs extends \IPS\Dispatcher\Controller
$node->description
);
}
/**
* Update Existing Purchases
*
* @return void
*/
public function updateExisting()
{
\IPS\Session::i()->csrfCheck();
try
{
$club = \IPS\Member\Club::load( \IPS\Request::i()->id );
}
catch ( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'no_module_permission', '3C352/8', 403, '' );
}
$changes = json_decode( \IPS\Request::i()->changes, TRUE );
\IPS\Task::queue( 'core', 'UpdateClubRenewals', array( 'changes' => $changes, 'club' => $club->id ), 5 );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=clubs&controller=clubs" ), 'saved' );
}
}
@@ -45,9 +45,11 @@ class _settings extends \IPS\Dispatcher\Controller
*/
protected function manage()
{
$fields = array( 'clubs_default_sort', 'clubs_header', 'clubs_locations', 'clubs_modperms', 'clubs_require_approval', 'form_header_club_display_settings', 'form_header_club_moderation', 'clubs_default_view', 'clubs_allow_view_change', 'club_nodes_in_apps', 'form_header_clubs_paid_settings', 'clubs_paid_on', '_allow_club_moderators', 'club_max_cover' );
$form = new \IPS\Helpers\Form;
$form->addHeader( 'club_settings' );
$form->add( new \IPS\Helpers\Form\YesNo( 'clubs_enabled_setting', \IPS\Settings::i()->clubs, FALSE, array( 'togglesOn' => array( 'clubs_default_sort', 'clubs_header', 'clubs_locations', 'clubs_modperms', 'clubs_require_approval', 'form_header_club_display_settings', 'form_header_club_moderation', 'clubs_default_view', 'clubs_allow_view_change', 'club_nodes_in_apps', 'form_header_clubs_paid_settings', 'clubs_paid_on', '_allow_club_moderators' ) ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'clubs_enabled_setting', \IPS\Settings::i()->clubs, FALSE, array( 'togglesOn' => $fields ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'clubs_require_approval', \IPS\Settings::i()->clubs_require_approval, FALSE, array(), NULL, NULL, NULL, 'clubs_require_approval' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'clubs_locations', \IPS\Settings::i()->clubs_locations, FALSE, array(), NULL, NULL, NULL, 'clubs_locations' ) );
if ( \IPS\Application::appIsEnabled( 'nexus' ) )
@@ -104,7 +106,7 @@ class _settings extends \IPS\Dispatcher\Controller
'delete' => 'club_modperm_delete',
'split_merge' => 'club_modperm_split_merge',
) ), NULL, NULL, NULL, 'clubs_modperms' ) );
if ( $values = $form->values() )
{
$values['clubs'] = $values['clubs_enabled_setting'];
@@ -83,7 +83,7 @@ class _ajax extends \IPS\Dispatcher\Controller
{
foreach ( $templateNames[$app] as $location => $items )
{
if ( $location === 'front' )
if ( $location === 'front' or $location === 'global' )
{
foreach ( $templateNames[$app][$location] as $path => $items )
{
@@ -137,8 +137,8 @@ class _ajax extends \IPS\Dispatcher\Controller
else
{
$templateNames = $skinSet->getRawCss( '', '', '', \IPS\Theme::RETURN_ALL_NO_CONTENT );
$templateBits = $skinSet->getRawCss( $t_app, 'front', NULL, \IPS\Theme::RETURN_ALL );
$templateBit = ( ! empty( $t_name ) ) ? $templateBits[ $t_app ]['front'][ $t_group ][ $t_name ] : null;
$templateBits = $skinSet->getRawCss( $t_app, ['global', 'front'], NULL, \IPS\Theme::RETURN_ALL );
$templateBit = ( ! empty( $t_name ) ) ? $templateBits[ $t_app ][ $t_location ][ $t_group ][ $t_name ] : null;
$templateBit['css_content'] = htmlentities( $templateBit['css_content'], ENT_DISALLOWED, 'UTF-8', TRUE );
}
@@ -54,63 +54,6 @@ class _icons extends \IPS\Dispatcher\Controller
$shareLogos = \IPS\Settings::i()->icons_sharer_logo ? json_decode( \IPS\Settings::i()->icons_sharer_logo, true ) : array();
$form->add( new \IPS\Helpers\Form\Upload( 'icons_sharer_logo', \count( $shareLogos ) ? array_map( function( $val ) { return \IPS\File::get( 'core_Icons', $val ); }, $shareLogos ) : array(), FALSE, array( 'image' => true, 'storageExtension' => 'core_Icons', 'multiple' => true ) ) );
/* Homescreen icons - we accept one upload and create the images we need */
$homeScreen = json_decode( \IPS\Settings::i()->icons_homescreen, TRUE ) ?? array();
$form->add( new \IPS\Helpers\Form\Upload( 'icons_homescreen', ( isset( $homeScreen['original'] ) ) ? \IPS\File::get( 'core_Icons', $homeScreen['original'] ) : NULL, FALSE, array( 'image' => true, 'storageExtension' => 'core_Icons' ) ) );
$homeScreenMaskable = json_decode( \IPS\Settings::i()->icons_homescreen_maskable, TRUE ) ?? array();
$form->add( new \IPS\Helpers\Form\Upload( 'icons_homescreen_maskable', ( isset( $homeScreenMaskable['original'] ) ) ? \IPS\File::get( 'core_Icons', $homeScreenMaskable['original'] ) : NULL, FALSE, array( 'image' => true, 'storageExtension' => 'core_Icons', 'allowedFileTypes' => ['png', 'webp' ] ) ) );
/* Apple startup screen logo - we accept one upload and create the images we need */
$apple = json_decode( \IPS\Settings::i()->icons_apple_startup, TRUE ) ?? array();
$form->add( new \IPS\Helpers\Form\Upload( 'icons_apple_startup', ( isset( $apple['original'] ) ) ? \IPS\File::get( 'core_Icons', $apple['original'] ) : NULL, FALSE, array( 'image' => true, 'storageExtension' => 'core_Icons' ) ) );
/* Safari pinned tabs icon and highlight color */
$form->add( new \IPS\Helpers\Form\Upload( 'icons_mask_icon', \IPS\Settings::i()->icons_mask_icon ? \IPS\File::get( 'core_Icons', \IPS\Settings::i()->icons_mask_icon ) : NULL, FALSE, array( 'allowedFileTypes' => array( 'svg' ), 'storageExtension' => 'core_Icons', 'obscure' => FALSE ) ) );
$form->add( new \IPS\Helpers\Form\Color( 'icons_mask_color', \IPS\Settings::i()->icons_mask_color, FALSE ) );
/* And finally, additional manifest and livetile details */
$manifestDetails = json_decode( \IPS\Settings::i()->manifest_details, TRUE );
$form->add( new \IPS\Helpers\Form\YesNo( 'configure_manifest', \count( $manifestDetails ) > 0, FALSE, array(
'togglesOn' => array( 'manifest_shortname', 'manifest_fullname', 'manifest_description', 'manifest_defaultapp', 'manifest_themecolor', 'manifest_bgcolor', 'manifest_display', 'manifest_custom_url_toggle' ),
) ) );
$form->add( new \IPS\Helpers\Form\Text( 'manifest_shortname', ( isset( $manifestDetails['short_name'] ) ) ? $manifestDetails['short_name'] : '', FALSE, array(), NULL, NULL, NULL, 'manifest_shortname' ) );
$form->add( new \IPS\Helpers\Form\Text( 'manifest_fullname', ( isset( $manifestDetails['name'] ) ) ? $manifestDetails['name'] : '', FALSE, array(), NULL, NULL, NULL, 'manifest_fullname' ) );
$form->add( new \IPS\Helpers\Form\TextArea( 'manifest_description', ( isset( $manifestDetails['description'] ) ) ? $manifestDetails['description'] : '', FALSE, array(), NULL, NULL, NULL, 'manifest_description' ) );
$formStartUrl = '';
if( isset( $manifestDetails['start_url'] ) )
{
$formStartUrl = str_replace( 'index.php?/', '', \IPS\Http\Url\Friendly::fixComponentPath( $manifestDetails['start_url'] ) );
}
$form->add( new \IPS\Helpers\Form\YesNo( 'manifest_custom_url_toggle', ( isset( $manifestDetails['start_url'] ) and empty( $formStartUrl ) ? FALSE : TRUE ), FALSE, array(
'togglesOn' => array( 'manifest_short_url' ),
), NULL, NULL, NULL, 'manifest_custom_url_toggle' ) );
$form->add( new \IPS\Helpers\Form\Text( 'manifest_short_url', $formStartUrl, FALSE, array(), function( $val )
{
if ( $val and \IPS\Request::i()->manifest_custom_url_toggle_checkbox )
{
if ( mb_substr( $val, -1 ) !== '/' )
{
$val .= '/';
}
$response = \IPS\Http\Url::external( \IPS\Http\Url::baseUrl() . ( \IPS\Settings::i()->htaccess_mod_rewrite ? $val : 'index.php?/' . $val ) )->request( NULL, NULL, FALSE )->get();
if ( $response->httpResponseCode != 200 and $response->httpResponseCode != 303 and ( \IPS\Settings::i()->site_online OR $response->httpResponseCode != 503 ) )
{
throw new \LogicException( 'pwa_start_url_incorrect' );
}
}
}, \IPS\Http\Url::baseUrl() . ( !\IPS\Settings::i()->htaccess_mod_rewrite ? 'index.php?/' : '' ), NULL, 'manifest_short_url' ) );
$form->add( new \IPS\Helpers\Form\Color( 'manifest_themecolor', ( isset( $manifestDetails['theme_color'] ) ) ? $manifestDetails['theme_color'] : NULL, FALSE, array(), NULL, NULL, NULL, 'manifest_themecolor' ) );
$form->add( new \IPS\Helpers\Form\Color( 'manifest_bgcolor', ( isset( $manifestDetails['background_color'] ) ) ? $manifestDetails['background_color'] : NULL, FALSE, array(), NULL, NULL, NULL, 'manifest_bgcolor' ) );
$form->add( new \IPS\Helpers\Form\Radio( 'manifest_display', ( isset( $manifestDetails['display'] ) ) ? $manifestDetails['display'] : 'standalone', FALSE, array( 'options' => array( 'fullscreen' => 'manifest_fullscreen', 'standalone' => 'manifest_standalone', 'minimal-ui' => 'manifest_minimalui', 'browser' => 'manifest_browser' ) ), NULL, NULL, NULL, 'manifest_display' ) );
/* We've submitted, check our values! */
if ( $values = $form->values() )
{
@@ -123,373 +66,20 @@ class _icons extends \IPS\Dispatcher\Controller
$values['icons_sharer_logo'] = json_encode( array_map( function( $val ){ return (string) $val; }, $values['icons_sharer_logo'] ) );
}
$path = \IPS\Http\Url::createFromString( \IPS\Http\Url::baseUrl() )->data[ \IPS\Http\Url::COMPONENT_PATH ];
$startUrl = $path ?? '';
if ( $values['manifest_custom_url_toggle'] !== FALSE and ! empty( $values['manifest_short_url'] ) )
{
$startUrl = '/' . trim( $values['manifest_short_url'], '/' ) . '/';
if( !empty( $path ) )
{
$startUrl = '/' . trim( $path . ( !\IPS\Settings::i()->htaccess_mod_rewrite ? 'index.php?/' : '' ) . ltrim( $values['manifest_short_url'], '/' ), '/' ) . '/';
}
}
/* Homescreen icon is the hardest part, as we need to generate different sizes.. */
$values = static::processApplicationIcon( $values, $homeScreen, $homeScreenMaskable );
/* The start screen requires more different sizes so we'll rebuild that after submit */
$rebuildStartScreen = FALSE;
if( $values['icons_apple_startup'] AND ( !isset( $apple['original'] ) OR !$apple['original'] OR (string) $values['icons_apple_startup'] != $apple['original'] ) )
{
$rebuildStartScreen = TRUE;
}
if( ( !isset( $manifestDetails['background_color'] ) AND $values['manifest_bgcolor'] ) OR ( isset( $manifestDetails['background_color'] ) AND !$values['manifest_bgcolor'] ) OR ( isset( $manifestDetails['background_color'] ) AND $values['manifest_bgcolor'] != $manifestDetails['background_color'] ) )
{
if( $values['icons_apple_startup'] )
{
$rebuildStartScreen = TRUE;
}
}
$values = static::processAppleStartupScreen( $values, $apple );
/* We need the string value of this uploaded file as well */
$values['icons_mask_icon'] = (string) $values['icons_mask_icon'];
/* Finally, handle the manifest details */
$values['manifest_details'] = array();
if( $values['configure_manifest'] )
{
$values['manifest_details']['short_name'] = $values['manifest_shortname'];
$values['manifest_details']['start_url'] = $startUrl;
$values['manifest_details']['name'] = $values['manifest_fullname'];
$values['manifest_details']['description'] = $values['manifest_description'];
$values['manifest_details']['theme_color'] = $values['manifest_themecolor'];
$values['manifest_details']['background_color'] = $values['manifest_bgcolor'];
$values['manifest_details']['display'] = $values['manifest_display'];
}
unset( $values['configure_manifest'], $values['manifest_shortname'], $values['manifest_fullname'], $values['manifest_description'], $values['manifest_display'], $values['manifest_bgcolor'], $values['manifest_themecolor'], $values['manifest_custom_url_toggle'], $values['manifest_short_url'] );
$values['manifest_details'] = json_encode( $values['manifest_details'] );
/* Save the settings */
$form->saveAsSettings( $values );
/* Clear guest page caches */
\IPS\Data\Cache::i()->clearAll();
/* Clear manifest and ie browser data stores */
unset( \IPS\Data\Store::i()->manifest, \IPS\Data\Store::i()->iebrowserconfig );
/* And log */
\IPS\Session::i()->log( 'acplogs__icons_and_logos' );
/* And Redirect */
if( $rebuildStartScreen === TRUE )
{
\IPS\Output::i()->redirect( $this->url->setQueryString( 'do', 'buildStartupScreenImages' ), 'saved' );
}
else
{
\IPS\Output::i()->redirect( $this->url, 'saved' );
}
\IPS\Output::i()->redirect( $this->url, 'saved' );
}
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_customization_icons');
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'global' )->block( 'menu__core_customization_icons', $form );
}
/**
* Process application icon
*
* @param array $values Values from form submission
* @param array $homeScreen Existing values, if any
* @param array $homeScreenMaskable Existing values, if any
* @return array
*/
public static function processApplicationIcon( $values, $homeScreen = array(), $homeScreenMaskable = array() )
{
if( ( isset( $values['icons_homescreen'] ) AND $values['icons_homescreen'] ) or ( isset( $values['icons_homescreen_maskable' ] ) AND $values['icons_homescreen_maskable'] ) )
{
foreach( [ 'icons_homescreen', 'icons_homescreen_maskable' ] as $type )
{
$setting = [];
if( isset( $values[ $type ] ) AND $values[ $type ] )
{
$sizes = array(
'android-chrome-36x36' => array(36, 36),
'android-chrome-48x48' => array(48, 48),
'android-chrome-72x72' => array(72, 72),
'android-chrome-96x96' => array(96, 96),
'android-chrome-144x144' => array(144, 144),
'android-chrome-192x192' => array(192, 192),
'android-chrome-256x256' => array(256, 256),
'android-chrome-384x384' => array(384, 384),
'android-chrome-512x512' => array(512, 512),
'msapplication-square70x70logo' => array(128, 128),
'msapplication-TileImage' => array(144, 144),
'msapplication-square150x150logo' => array(270, 270),
'msapplication-wide310x150logo' => array(558, 558),
'msapplication-square310x310logo' => array(558, 270),
'apple-touch-icon-57x57' => array(57, 57),
'apple-touch-icon-60x60' => array(60, 60),
'apple-touch-icon-72x72' => array(72, 72),
'apple-touch-icon-76x76' => array(76, 76),
'apple-touch-icon-114x114' => array(114, 114),
'apple-touch-icon-120x120' => array(120, 120),
'apple-touch-icon-144x144' => array(144, 144),
'apple-touch-icon-152x152' => array(152, 152),
'apple-touch-icon-180x180' => array(180, 180),
);
$setting = array('original' => (string)$values[ $type ]);
foreach ( $sizes as $filename => $_sizes )
{
if ( $type == 'icons_homescreen_maskable' )
{
$filename .= "-masked";
if ( ! \mb_strstr( $filename, 'android-chrome' ) )
{
continue;
}
}
try
{
$image = \IPS\Image::create( $values[ $type ]->contents() );
if ( $image::exifSupported() )
{
$image->setExifData( $values[ $type ]->contents() );
}
$image->crop( $_sizes[0], $_sizes[1] );
$setting[$filename] = array(
'url' => (string)\IPS\File::create( 'core_Icons', $filename . '.png', (string)$image, NULL, TRUE, NULL, FALSE ),
'width' => $image->width,
'height' => $image->height
);
}
catch ( \Exception $e )
{
}
}
}
$values[ $type ] = json_encode( $setting );
}
}
else
{
/* Delete any images that may already exist */
foreach( $homeScreen as $key => $image )
{
try
{
\IPS\File::get( 'core_Icons', ( $key == 'original' ) ? $image : $image['url'] )->delete();
}
catch( \Exception $e ){}
}
foreach( $homeScreenMaskable as $key => $image )
{
try
{
\IPS\File::get( 'core_Icons', ( $key == 'original' ) ? $image : $image['url'] )->delete();
}
catch( \Exception $e ){}
}
$values['icons_homescreen'] = '';
}
return $values;
}
/**
* @brief Minimum padding on either side of startup image (in px)
*/
const MINIMUM_STARTUP_IMAGE_PADDING = 50;
/**
* Process Apple startup screen images
*
* @param array $values Values from form submission
* @param array $apple Existing values, if any
* @return array
*/
public static function processAppleStartupScreen( $values, $apple = array() )
{
if( $values['icons_apple_startup'] )
{
$values['icons_apple_startup'] = json_encode( array( 'original' => (string) $values['icons_apple_startup'] ) );
}
else
{
/* Delete any images that may already exist */
foreach( $apple as $key => $image )
{
try
{
\IPS\File::get( 'core_Icons', ( $key == 'original' ) ? $image : $image['url'] )->delete();
}
catch( \Exception $e ){}
}
$values['icons_apple_startup'] = '';
}
return $values;
}
/**
* Process Apple startup screen images
*
* @param array $values Values from form submission
* @param array $apple Existing values, if any
* @return array
*/
public function buildStartupScreenImages()
{
$self = $this;
$multiRedirect = new \IPS\Helpers\MultipleRedirect(
$this->url->setQueryString('do', 'buildStartupScreenImages'),
function( $data )
{
/* Get the necessary data */
$manifestDetails = json_decode( \IPS\Settings::i()->manifest_details, TRUE );
$setting = json_decode( \IPS\Settings::i()->icons_apple_startup, TRUE ) ?? array();
if( isset( $setting['original'] ) )
{
$sizes = array(
'apple-startup-1136x640' => array( 320, 568, 2, 'landscape' ),
'apple-startup-2436x1125' => array( 375, 812, 3, 'landscape' ),
'apple-startup-1792x828' => array( 414, 896, 2, 'landscape' ),
'apple-startup-828x1792' => array( 414, 896, 2, 'portrait' ),
'apple-startup-1334x750' => array( 375, 667, 2, 'landscape' ),
'apple-startup-1242x2688' => array( 414, 896, 3, 'portrait' ),
'apple-startup-2208x1242' => array( 414, 736, 3, 'landscape' ),
'apple-startup-1125x2436' => array( 375, 812, 3, 'portrait' ),
'apple-startup-1242x2208' => array( 414, 736, 3, 'portrait' ),
'apple-startup-2732x2048' => array( 1024, 1366, 2, 'landscape' ),
'apple-startup-2688x1242' => array( 414, 896, 3, 'landscape' ),
'apple-startup-2224x1668' => array( 834, 1112, 2, 'landscape' ),
'apple-startup-750x1334' => array( 375, 667, 2, 'portrait' ),
'apple-startup-2048x2732' => array( 1024, 1366, 2, 'portrait' ),
'apple-startup-2388x1668' => array( 834, 1194, 2, 'landscape' ),
'apple-startup-1668x2224' => array( 834, 1112, 2, 'portrait' ),
'apple-startup-640x1136' => array( 320, 568, 2, 'portrait' ),
'apple-startup-1668x2388' => array( 834, 1194, 2, 'portrait' ),
'apple-startup-2048x1536' => array( 768, 1024, 2, 'landscape' ),
'apple-startup-1536x2048' => array( 768, 1024, 2, 'portrait' ),
'apple-startup-2360x1640' => array( 1180, 820, 2, 'landscape' ),
'apple-startup-1640x2360' => array( 1180, 820, 2, 'portrait' ),
'apple-startup-2160x1620' => array( 1080, 810, 2, 'landscape' ),
'apple-startup-1620x2160' => array( 1080, 810, 2, 'portrait' ),
'apple-startup-2778x1284' => array( 428, 926, 3, 'landscape' ),
'apple-startup-1284x2778' => array( 428, 926, 3, 'portrait' ),
'apple-startup-2532x1170' => array( 390, 844, 3, 'landscape' ),
'apple-startup-1170x2532' => array( 390, 844, 3, 'portrait' ),
'apple-startup-2340x1080' => array( 360, 780, 3, 'landscape' ),
'apple-startup-1080x2340' => array( 360, 780, 3, 'portrait' ),
);
$file = \IPS\File::get( 'core_Icons', $setting['original'] );
$originalContents = $file->contents();
$backgroundColor = ( isset( $manifestDetails['background_color'] ) ) ? str_replace( '#', '', $manifestDetails['background_color'] ) : 'FFFFFF';
$rgb = array();
if ( \strlen( $backgroundColor ) == 3 )
{
$rgb[] = hexdec( \substr( $backgroundColor, 0, 1 ) . \substr( $backgroundColor, 0, 1 ) ); // R
$rgb[] = hexdec( \substr( $backgroundColor, 1, 1 ) . \substr( $backgroundColor, 1, 1 ) ); // G
$rgb[] = hexdec( \substr( $backgroundColor, 2, 1 ) . \substr( $backgroundColor, 2, 1 ) ); // B
}
else
{
$rgb[] = hexdec( \substr( $backgroundColor, 0, 2 ) ); // R
$rgb[] = hexdec( \substr( $backgroundColor, 2, 2 ) ); // G
$rgb[] = hexdec( \substr( $backgroundColor, 4, 2 ) ); // B
}
$did = 0;
foreach( $sizes as $filename => $_sizes )
{
$did++;
if( isset( $setting[ $filename ] ) )
{
continue;
}
try
{
$width = ( $_sizes[3] == 'landscape' ) ? $_sizes[1] * $_sizes[2] : $_sizes[0] * $_sizes[2];
$height = ( $_sizes[3] == 'portrait' ) ? $_sizes[1] * $_sizes[2] : $_sizes[0] * $_sizes[2];
$logoImage = \IPS\Image::create( $originalContents );
$canvas = \IPS\Image::newImageCanvas( $width, $height, $rgb );
$logoImage->resizeToMax( $width - static::MINIMUM_STARTUP_IMAGE_PADDING, $height - static::MINIMUM_STARTUP_IMAGE_PADDING );
$xPos = ( $width - $logoImage->width ) / 2;
$yPos = ( $height - $logoImage->height ) / 2;
$canvas->impose( $logoImage, $xPos, $yPos );
if( $canvas::exifSupported() )
{
$canvas->setExifData( $originalContents );
}
$setting[ $filename ] = array(
'url' => (string) \IPS\File::create( 'core_Icons', $filename . '.png', (string) $canvas, NULL, TRUE, NULL, FALSE ),
'width' => $canvas->width,
'height' => $canvas->height,
'density' => $_sizes[2],
'orientation' => $_sizes[3],
);
}
catch ( \Exception $e )
{
\IPS\Log::log( $e, 'apple-startup-image' );
}
break;
}
/* Are we done? */
if( $did == \count( $sizes ) )
{
return NULL;
}
\IPS\Settings::i()->changeValues( array( 'icons_apple_startup' => json_encode( $setting ) ) );
}
return array( $data, \IPS\Member::loggedIn()->language()->addToStack('build_start_images_title'), ( $did ) ? round( ( 100 / \count( $sizes ) * $did ), 2 ) : 0 );
},
function() use( $self )
{
\IPS\Output::i()->redirect( $self->url, 'completed' );
}
);
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('build_start_images_title');
\IPS\Output::i()->output = $multiRedirect;
}
}
@@ -66,6 +66,18 @@ class _themes extends \IPS\Node\Controller
*/
protected function manage()
{
if ( !\IPS\Request::i()->isAjax() AND \IPS\IPS::canManageResources() )
{
foreach( \IPS\Theme::themes() as $app )
{
if( $app->marketplace_id )
{
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate('marketplace')->removalBanner(FALSE);
break;
}
}
}
if ( \IPS\Theme::designersModeEnabled() and ! isset( \IPS\Request::i()->root ) )
{
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->message( \IPS\Member::loggedIn()->language()->addToStack('theme_designer_mode_warning'), 'information', NULL, FALSE );
@@ -1100,7 +1112,7 @@ class _themes extends \IPS\Node\Controller
}
\IPS\Output::i()->output = new \IPS\Helpers\MultipleRedirect(
\IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=import' )->setQueryString( array( 'file' => \IPS\Request::i()->file, 'key' => \IPS\Request::i()->key, 'id' => \IPS\Request::i()->id ) )->csrf(),
\IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=import' )->setQueryString( array( 'file' => \IPS\Request::i()->file, 'key' => \IPS\Request::i()->key, 'id' => \IPS\Request::i()->id, 'manual' => \IPS\Request::i()->manual ) )->csrf(),
function( $data )
{
$set = \IPS\Theme::load( \IPS\Request::i()->id );
@@ -1124,6 +1136,13 @@ class _themes extends \IPS\Node\Controller
if ( !\is_array( $data ) )
{
$_SESSION['theme_import'] = array( 'css' => array(), 'isNewSet' => false );
/* Remove marketplace link */
if( \IPS\Theme::load( \IPS\Request::i()->id )->marketplace_id AND \IPS\Request::i()->manual )
{
\IPS\Theme::load( \IPS\Request::i()->id )->marketplace_id = NULL;
\IPS\Theme::load( \IPS\Request::i()->id )->save();
}
/* Save snapshot */
\IPS\Theme::load( \IPS\Request::i()->id )->saveHistorySnapshot();
@@ -1859,11 +1878,6 @@ class _themes extends \IPS\Node\Controller
$themeSet = \IPS\Theme::load( $id );
$apps = array();
if( $themeSet->marketplace_id )
{
\IPS\Output::i()->error( 'app_upload_marketplace_only', '2C163/K', 403, '' );
}
foreach( \IPS\Application::applications() as $key => $data )
{
@@ -1872,6 +1886,11 @@ class _themes extends \IPS\Node\Controller
}
$form = new \IPS\Helpers\Form( 'form', 'theme_set_import_button' );
if( $themeSet->marketplace_id )
{
$form->addMessage( 'marketplace_resource_manual_upgrade', 'ipsMessage ipsMessage_info' );
}
$form->addMessage('themes_manual_install_warning');
$form->add( new \IPS\Helpers\Form\Upload( 'core_theme_set_new_import', NULL, FALSE, array( 'allowedFileTypes' => array( 'xml' ), 'temporary' => TRUE ), NULL, NULL, NULL, 'core_theme_set_new_import' ) );
@@ -1882,7 +1901,7 @@ class _themes extends \IPS\Node\Controller
move_uploaded_file( $values['core_theme_set_new_import'], $tempFile );
/* Initate a redirector */
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=import' )->setQueryString( array( 'file' => $tempFile, 'key' => md5_file( $tempFile ), 'id' => \IPS\Request::i()->id) )->csrf() );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=import' )->setQueryString( array( 'file' => $tempFile, 'key' => md5_file( $tempFile ), 'id' => \IPS\Request::i()->id, 'manual' => 1 ) )->csrf() );
}
/* Display */
@@ -2610,6 +2629,12 @@ class _themes extends \IPS\Node\Controller
/* Extra template only variables */
$save['attributes'] = \IPS\Request::i()->$attributesName;
/* This is likely a customization of a core CSS file which was allowed previously, but now is not. So let's inform the user it cannot be saved */
if ( $save['app'] === 'core' and $save['location'] === 'global' )
{
\IPS\Output::i()->error( 'cant_save_global_css_changes_now_sorry', '3C163/Z', 500, '' );
}
$newId = \IPS\Theme::load( $save['set_id'] )->saveCss( $save );
}
@@ -2992,26 +3017,23 @@ class _themes extends \IPS\Node\Controller
if ( $values = $form->values() )
{
if ( \IPS\Request::i()->isAjax() )
/* Test the syntax */
$errors = [];
foreach( [ 'header', 'footer', 'css' ] as $key )
{
/* Test the syntax */
$errors = [];
foreach( [ 'header', 'footer', 'css' ] as $key )
try
{
try
{
\IPS\Theme::checkTemplateSyntax( $values['theme_simple_' . $key ] );
}
catch( \LogicException $e )
{
$errors[] = \IPS\Member::loggedIn()->language()->addToStack( 'theme_error_in_template', NULL, [ 'sprintf' => [ \IPS\Member::loggedIn()->language()->get('theme_simple_' . $key . '_tab') ] ] );
}
\IPS\Theme::checkTemplateSyntax( $values['theme_simple_' . $key ] );
}
catch( \LogicException $e )
{
$errors[] = \IPS\Member::loggedIn()->language()->addToStack( 'theme_error_in_template', NULL, [ 'sprintf' => [ \IPS\Member::loggedIn()->language()->get('theme_simple_' . $key . '_tab') ] ] );
}
}
if ( \count( $errors ) )
{
\IPS\Output::i()->error( implode( ". ", $errors ), '3C163/I', 500, '' );
}
if ( \count( $errors ) )
{
\IPS\Output::i()->error( implode( ". ", $errors ), '3C163/I', 500, '' );
}
$themeSet->custom_header = $values['theme_simple_header'];
@@ -3144,7 +3166,7 @@ class _themes extends \IPS\Node\Controller
{
foreach ( $templateNames[$app] as $location => $items )
{
if ( $location === 'front' )
if ( $location === 'front' or $location === 'global' )
{
foreach ( $templateNames[$app][$location] as $path => $items )
{
@@ -84,6 +84,18 @@ class _languages extends \IPS\Node\Controller
protected function manage()
{
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'flags.css', 'core', 'global' ) );
if ( !\IPS\Request::i()->isAjax() AND \IPS\IPS::canManageResources() )
{
foreach ( \IPS\Lang::languages() as $app )
{
if ( $app->marketplace_id )
{
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'marketplace' )->removalBanner( FALSE );
break;
}
}
}
if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'languages', 'lang_words' ) )
{
@@ -187,7 +199,6 @@ class _languages extends \IPS\Node\Controller
{
/* Build form */
$form = new \IPS\Helpers\Form;
$form->addMessage('languages_manual_install_warning');
$form->add( new \IPS\Helpers\Form\Upload( 'lang_upload', NULL, TRUE, array( 'allowedFileTypes' => array( 'xml' ), 'temporary' => TRUE ) ) );
\IPS\Lang::localeField( $form );
@@ -380,6 +391,9 @@ class _languages extends \IPS\Node\Controller
/* Clear guest page caches */
\IPS\Data\Cache::i()->clearAll();
/* Update the essential cookie name list */
unset( \IPS\Data\Store::i()->essentialCookieNames );
/* Log */
if ( \IPS\Request::i()->status )
{
@@ -464,7 +478,7 @@ class _languages extends \IPS\Node\Controller
*/
protected function translate()
{
if ( isset( \IPS\Request::i()->cookie['vle_editor'] ) and \IPS\Request::i()->cookie['vle_editor'] )
if ( \IPS\Lang::vleActive() )
{
\IPS\Output::i()->error( 'no_translate_with_vle', '1C126/8', 403, '' );
}
@@ -531,6 +545,10 @@ class _languages extends \IPS\Node\Controller
return \IPS\Theme::i()->getTemplate( 'global' )->shortMessage( \IPS\Member::loggedIn()->language()->addToStack('translate_na'), array( 'ipsBadge', 'ipsBadge_neutral' ) );
}
},
'word_default' => function( $val, $row )
{
return htmlspecialchars( $val, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', TRUE );
},
'word_custom' => function( $val, $row )
{
return \IPS\Theme::i()->getTemplate( 'customization' )->langString( $val, $row['word_key'], $row['lang_id'], $row['word_js'] );
@@ -717,6 +735,9 @@ class _languages extends \IPS\Node\Controller
unset( \IPS\Data\Store::i()->languages );
unset( \IPS\Data\Store::i()->listFormats );
/* Update the essential cookie name list */
unset( \IPS\Data\Store::i()->essentialCookieNames );
/* Clear guest page caches */
\IPS\Data\Cache::i()->clearAll();
@@ -873,13 +894,12 @@ class _languages extends \IPS\Node\Controller
\IPS\Dispatcher::i()->checkAcpPermission( 'lang_words' );
$language = \IPS\Lang::load( \IPS\Request::i()->id );
if( $language->marketplace_id )
{
\IPS\Output::i()->error( 'app_upload_marketplace_only', '2C126/B', 403, '' );
}
$form = new \IPS\Helpers\Form;
if( $language->marketplace_id )
{
$form->addMessage( 'marketplace_resource_manual_upgrade', 'ipsMessage ipsMessage_info' );
}
$form->add( new \IPS\Helpers\Form\Upload( 'lang_upload', NULL, TRUE, array( 'allowedFileTypes' => array( 'xml' ), 'temporary' => TRUE ) ) );
/* Handle submissions */
@@ -1059,6 +1079,9 @@ class _languages extends \IPS\Node\Controller
/* Clear guest page caches */
\IPS\Data\Cache::i()->clearAll();
/* Update the essential cookie name list */
unset( \IPS\Data\Store::i()->essentialCookieNames );
@unlink( \IPS\Request::i()->file );
/* Redirect back to marketplace if it was installed from there. */
@@ -1066,6 +1089,11 @@ class _languages extends \IPS\Node\Controller
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=marketplace&controller=marketplace&do=viewFile&id=' . \IPS\Request::i()->marketplace ), 'lang_installed' );
}
elseif ( isset( \IPS\Request::i()->into ) )
{
\IPS\Lang::load( (int) \IPS\Request::i()->into )->marketplace_id = NULL;
\IPS\Lang::load( (int) \IPS\Request::i()->into )->save();
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=languages&controller=languages' ) );
}
@@ -26,6 +26,76 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* Get the available filters for the main table
*
* @return array
*/
protected static function _getTableFilters() : array
{
$filters = [
'members_filter_banned' => 'temp_ban<>0',
'members_filter_spam' => \IPS\Db::i()->bitwiseWhere( \IPS\Member::$bitOptions['members_bitoptions'], 'bw_is_spammer' ),
'members_filter_validating' => '( v.lost_pass=0 AND v.forgot_security=0 AND v.vid IS NOT NULL )',
'members_filter_administrators' => '(1=1)' // We use a straight join when this filter is activated, so there's no additional where needed
];
if ( \IPS\Db::i()->select( 'COUNT(*)', 'core_members', array( "email <> '' and name=''" ) )->first() )
{
$filters['members_filtered_reserved'] = "(name='')";
}
if( \IPS\Settings::i()->ipb_bruteforce_attempts )
{
/* We do this so we can put the locked filter at the 'end' of the buttons - array_unshift does not give us a simple method to retain keys */
$filters += array( 'members_filter_locked' => '( failed_login_count>=' . (int) \IPS\Settings::i()->ipb_bruteforce_attempts . ' OR failed_mfa_attempts>=' . (int) \IPS\Settings::i()->security_questions_tries . ')' );
}
return $filters;
}
/**
* Get the joins for the main table
*
* @returns array
*/
protected static function _getTableJoins( array $joinFields, \IPS\Helpers\Table\Db $table ) : array
{
$joins = [
array(
'select' => 'v.vid, v.coppa_user, v.lost_pass, v.forgot_security, v.new_reg, v.email_chg, v.user_verified, v.spam_flag, v.reg_cancelled',
'from' => array( 'core_validating', 'v' ),
'where' => 'v.member_id=core_members.member_id AND v.lost_pass != 1 AND v.forgot_security != 1' ),
array(
'select' => implode( ',', $joinFields ),
'from' => array( 'core_pfields_content', 'p' ),
'where' => 'p.member_id=core_members.member_id' ),
array(
'select' => 'm.row_id',
'from' => array( 'core_admin_permission_rows', 'm' ),
'where' => "( m.row_id_type='group' and ( m.row_id=core_members.member_group_id OR FIND_IN_SET( m.row_id, core_members.mgroup_others ) ) or ( m.row_id=core_members.member_id AND m.row_id_type='member' ))",
'type' => 'STRAIGHT_JOIN' )
];
/* Makes query less efficient */
if ( $table->filter !== 'members_filter_administrators' )
{
unset( $joins[2] );
}
if ( $table->filter !== 'members_filter_validating' )
{
unset( $joins[0] );
}
if ( ! isset( \IPS\Request::i()->advanced_search_submitted ) )
{
unset( $joins[1] );
}
return $joins;
}
/**
* Manage Members
@@ -51,7 +121,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
}
/* Create the table */
$table = new \IPS\Helpers\Table\Db( 'core_members', \IPS\Http\Url::internal( 'app=core&module=members&controller=members' ), array( array( 'email<>?', '' ) ), $forceIndex );
$table = new \IPS\Helpers\Table\Db( 'core_members', \IPS\Http\Url::internal( 'app=core&module=members&controller=members' ), array( array( 'core_members.email<>?', '' ) ), $forceIndex );
$table->langPrefix = 'members_';
/* Columns we need */
@@ -149,37 +219,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
}
/* Joins */
$table->joins = array(
array(
'select' => 'v.vid, v.coppa_user, v.lost_pass, v.forgot_security, v.new_reg, v.email_chg, v.user_verified, v.spam_flag, v.reg_cancelled',
'from' => array( 'core_validating', 'v' ),
'where' => 'v.member_id=core_members.member_id AND v.lost_pass != 1 AND v.forgot_security != 1' ),
array(
'select' => implode( ',', $joinFields ),
'from' => array( 'core_pfields_content', 'p' ),
'where' => 'p.member_id=core_members.member_id' ),
array(
'select' => 'm.row_id',
'from' => array( 'core_admin_permission_rows', 'm' ),
'where' => "( m.row_id_type='group' and ( m.row_id=core_members.member_group_id OR FIND_IN_SET( m.row_id, core_members.mgroup_others ) ) or ( m.row_id=core_members.member_id AND m.row_id_type='member' ))",
'type' => 'STRAIGHT_JOIN' )
);
/* Makes query less efficient */
if ( $table->filter !== 'members_filter_administrators' )
{
unset( $table->joins[2] );
}
if ( $table->filter !== 'members_filter_validating' )
{
unset( $table->joins[0] );
}
if ( ! isset( \IPS\Request::i()->advanced_search_submitted ) )
{
unset( $table->joins[1] );
}
$table->joins = static::_getTableJoins( $joinFields, $table );
/* Ranks */
$rankOptions = array( '' => 'any' );
@@ -206,7 +246,10 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
return array( "core_members.member_id=?", (float) $v[1] );
}
} ),
'email' => \IPS\Helpers\Table\SEARCH_CONTAINS_TEXT,
'email' => array( \IPS\Helpers\Table\SEARCH_CONTAINS_TEXT, array(), function( $val )
{
return array( "core_members.email LIKE ?", '%' . $val . '%' );
} ),
'ip_address' => array( \IPS\Helpers\Table\SEARCH_CONTAINS_TEXT, array(), function( $val )
{
return array( "core_members.ip_address LIKE ?", '%' . $val . '%' );
@@ -266,23 +309,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
}
/* Filters */
$table->filters = array(
'members_filter_banned' => 'temp_ban<>0',
'members_filter_spam' => \IPS\Db::i()->bitwiseWhere( \IPS\Member::$bitOptions['members_bitoptions'], 'bw_is_spammer' ),
'members_filter_validating' => '( v.lost_pass=0 AND v.forgot_security=0 AND v.vid IS NOT NULL )',
'members_filter_administrators' => '(1=1)' // We use a straight join when this filter is activated, so there's no additional where needed
);
if ( \IPS\Db::i()->select( 'COUNT(*)', 'core_members', array( "email <> '' and name=''" ) )->first() )
{
$table->filters['members_filtered_reserved'] = "(name='')";
}
if( \IPS\Settings::i()->ipb_bruteforce_attempts )
{
/* We do this so we can put the locked filter at the 'end' of the buttons - array_unshift does not give us a simple method to retain keys */
$table->filters = array( 'members_filter_locked' => '( failed_login_count>=' . (int) \IPS\Settings::i()->ipb_bruteforce_attempts . ' OR failed_mfa_attempts>=' . (int) \IPS\Settings::i()->security_questions_tries . ')' ) + $table->filters;
}
$table->filters = static::_getTableFilters();
/* Custom parsers */
$table->parsers = array(
@@ -2896,7 +2923,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
unset( \IPS\Data\Store::i()->bannedIpAddresses );
}
$member->save();
\IPS\Session::i()->log( 'acplog__members_edited', array( $member->name => FALSE ) );
@@ -4272,8 +4299,8 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
}
/* Cells starting with =, + or - can be a security risk. */
if ( !isset( $wizardData['includeInsecure'] ) and !\in_array( $column, array( 'primary_group_name', 'secondary_group_names', 'skin_name', 'language_name', 'members_pass_hash', 'members_pass_salt' ) ) and \in_array( mb_substr( $valueToWrite, 0, 1 ), array( '=', '+', '-', '@' ) ) )
{
if ( !isset( $wizardData['includeInsecure'] ) and !\in_array( $column, array( 'primary_group_name', 'secondary_group_names', 'skin_name', 'language_name', 'members_pass_hash', 'members_pass_salt' ) ) and \in_array( mb_substr( $valueToWrite, 0, 1 ), array( '=', '+', '-', '@' ) ) and ! is_numeric( \str_replace( ' ', '', $valueToWrite ) ) )
{
$mrData['removedData'][ $member['member_id'] ] = array( $column, base64_encode( $valueToWrite ) );
return FALSE;
}
@@ -4378,22 +4405,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
}
else
{
/* Init */
$xml = \IPS\Xml\SimpleXML::create('member_export');
$xml->addAttribute( 'created', \IPS\DateTime::ts( time() )->rfc3339() );
/* Get the data */
foreach( \IPS\Application::allExtensions( 'core', 'MemberExportPersonalInformation', TRUE, 'core' ) AS $key => $ext )
{
if ( $data = $ext->getData( $member ) and \is_array( $data ) and \count( $data ) )
{
$child = $xml->addChild( $key );
foreach( $data as $k => $v )
{
$child->addChild( $k, $v );
}
}
}
$xml = $member->getPiiData( $member );
\IPS\Session::i()->log( 'acplog__member_pii_exported', array( $member->name => FALSE ) );
@@ -0,0 +1,300 @@
<?php
/**
* @brief privacy
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @since 07 Mar 2023
*/
namespace IPS\core\modules\admin\members;
/* To prevent PHP errors (extending class does not exist) revealing path */
use IPS\Member;
use IPS\Output;
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* privacy
*/
class _privacy extends \IPS\Dispatcher\Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'privacy_manage' );
\IPS\Output::i()->sidebar['actions']['settings'] = array(
'icon' => 'cog',
'title' => 'settings',
'link' => $this->url->setQueryString( 'do', 'settings' ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('settings') )
);
parent::execute();
}
/**
* Manage
*
* @return void
*/
protected function manage()
{
/* Some advanced search links may bring us here */
\IPS\Output::i()->bypassCsrfKeyCheck = true;
/* Create the table */
$where = [ [ \IPS\Db::i()->in('action', [\IPS\Member\PrivacyAction::TYPE_REQUEST_DELETE, \IPS\Member\PrivacyAction::TYPE_REQUEST_PII] ) ], ['approved=?', 0] ];
$table = new \IPS\Helpers\Table\Db( 'core_member_privacy_actions', \IPS\Http\Url::internal( 'app=core&module=members&controller=privacy' ), $where );
$table->include = [ 'photo', 'name', 'email', 'joined','action','request_date' ];
$table->filters = [
'deletion_request' => ['action=?', \IPS\Member\PrivacyAction::TYPE_REQUEST_DELETE],
'pii_data' => ['action=?', \IPS\Member\PrivacyAction::TYPE_REQUEST_PII],
];
$table->joins = [
[ 'select' => 'm.*', 'from' => [ 'core_members', 'm' ], 'where' => 'core_member_privacy_actions.member_id=m.member_id' ]
];
$table->parsers = [
'photo' => function( $val, $row )
{
return \IPS\Theme::i()->getTemplate( 'global', 'core' )->userPhoto( \IPS\Member::constructFromData( $row ), 'tiny' );
},
'name' => function( $val, $row ){
if( $val )
{
$member = \IPS\Member::constructFromData( $row );
if( $banned = $member->isBanned() )
{
if( $banned instanceof \IPS\DateTime )
{
$title = \IPS\Member::loggedIn()->language()->addToStack( 'suspended_until', FALSE, array( 'sprintf' => array( $banned->localeDate() ) ) );
}else
{
$title = \IPS\Member::loggedIn()->language()->addToStack( 'banned' );
}
return "<a href='".\IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=view&id=' ).$row[ 'member_id' ]."'>".htmlentities( $val, ENT_DISALLOWED, 'UTF-8', FALSE )."</a> &nbsp; <span class='ipsBadge ipsBadge_negative'>".$title.'</span> ';
}else
{
return "<a href='".\IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=view&id=' ).$row[ 'member_id' ]."'>".htmlentities( $val, ENT_DISALLOWED, 'UTF-8', FALSE ).'</a>';
}
}
else
{
return \IPS\Theme::i()->getTemplate( 'members', 'core', 'admin' )->memberReserved( \IPS\Member::constructFromData( $row ) );
}
},
'joined' => function( $val, $row )
{
return \IPS\DateTime::ts( $val )->localeDate();
},
'request_date' => function( $val, $row )
{
return \IPS\DateTime::ts( $val )->localeDate();
},
'action' => function( $val, $row )
{
switch ( $val )
{
case \IPS\Member\PrivacyAction::TYPE_REQUEST_PII:
return \IPS\Member::loggedIn()->language()->addToStack('pii_download_requested');
case \IPS\Member\PrivacyAction::TYPE_REQUEST_DELETE:
return \IPS\Member::loggedIn()->language()->addToStack('account_deletion_requested');
}
}
];
$table->rowButtons = function( $row )
{
$return = [];
if( $row['action'] == \IPS\Member\PrivacyAction::TYPE_REQUEST_PII )
{
$return[ 'approve' ] = [
'title' => 'approve',
'icon' => 'check-circle',
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=privacy&do=approvePii&id='.$row[ 'id' ] )->csrf()->getSafeUrlFromFilters(),
];
$return['reject'] = [
'icon' => 'times',
'title' => 'reject',
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=privacy&do=rejectPii&id=' . $row['id'] )->csrf()->getSafeUrlFromFilters(),
];
}
else if( $row['action'] == \IPS\Member\PrivacyAction::TYPE_REQUEST_DELETE )
{
$member = \IPS\Member::constructFromData( $row );
$return[ 'approveDeletion' ] = [
'title' => 'approve',
'icon' => 'check-circle',
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=privacy&do=approveDeletion&id='.$row[ 'id' ] )->csrf()->getSafeUrlFromFilters(),
'data' => [ 'confirm' => '', 'confirmSubMessage' => \IPS\Member::loggedIn()->language()->addToStack( 'delete_member_confirm', FALSE, array( 'sprintf' => $member->name ) ) ]
];
$return['rejectDeletion'] = [
'icon' => 'times',
'title' => 'reject',
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=privacy&do=rejectDeletion&id=' . $row['id'] )->csrf()->getSafeUrlFromFilters(),
'data' => array(
)
];
}
return $return;
};
/* Display */
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'menu__core_members_privacy');
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global', 'core' )->block( 'title', (string) $table );
}
/**
* Approve PII Request
*
* @return void
*/
protected function approvePii()
{
\IPS\Session::i()->csrfCheck();
try
{
$request = \IPS\Member\PrivacyAction::load( \IPS\Request::i()->id );
$request->approvePiiRequest();
\IPS\Session::i()->log( 'acplog__piirequest_approved', array( $request->member->name => FALSE ) );
}
catch( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2C432/1', 404, '' );
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=members&controller=privacy' )->getSafeUrlFromFilters(), 'approved' );
}
/**
* Reject PII Request
*
* @return void
*/
protected function rejectPii()
{
\IPS\Session::i()->csrfCheck();
try
{
$request = \IPS\Member\PrivacyAction::load( \IPS\Request::i()->id );
$request->rejectPiiRequest();
\IPS\Session::i()->log( 'acplog__piirequest_rejected', array( $request->member->name => FALSE ) );
}
catch( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2C432/2', 404, '' );
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=members&controller=privacy' )->getSafeUrlFromFilters(), 'pii_request_rejected' );
}
/**
* Approve deletion request
*
* @return void
*/
protected function approveDeletion()
{
\IPS\Session::i()->csrfCheck();
try
{
$request = \IPS\Member\PrivacyAction::load( \IPS\Request::i()->id );
\IPS\Request::i()->confirmedDelete( message: \IPS\Member::loggedIn()->language()->addToStack( 'delete_member_confirm', FALSE, array( 'sprintf' => $request->member->name ) ) );
$request->deleteAccount();
\IPS\Session::i()->log( 'acplog__deletionrequest__approved', array( $request->member->name => FALSE ) );
}
catch( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2C432/3', 404, '' );
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=members&controller=privacy' )->getSafeUrlFromFilters(), 'approved' );
}
/**
* Reject deletion request
*
* @return void
*/
protected function rejectDeletion()
{
\IPS\Session::i()->csrfCheck();
try
{
$request = \IPS\Member\PrivacyAction::load( \IPS\Request::i()->id );
$request->rejectDeletionRequest();
\IPS\Session::i()->log( 'acplog__deletionrequest__rejected', array( $request->member->name => FALSE ) );
}
catch( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2C432/4', 404, '' );
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=members&controller=privacy' )->getSafeUrlFromFilters(), 'rejected' );
}
protected function settings()
{
$form = new \IPS\Helpers\Form();
$form->add( new \IPS\Helpers\Form\Radio( 'pii_type', \IPS\Settings::i()->pii_type, FALSE, array(
'options' => array(
'off' => 'disabled',
'on' => 'enabled',
'redirect' => "pii_external" ),
'toggles' => array(
'off' => array( '' ),
'redirect' => array( 'pii_link' ),
'on' => array(),
)
) ) );
$form->add( new \IPS\Helpers\Form\Url( 'pii_link', \IPS\Settings::i()->pii_link, FALSE, array(), NULL, NULL, NULL, 'pii_link' ) );
$form->add( new \IPS\Helpers\Form\Radio( 'right_to_be_forgotten_type', \IPS\Settings::i()->right_to_be_forgotten_type, FALSE, array(
'options' => array(
'off' => 'disabled',
'on' => 'enabled',
'redirect' => "right_to_be_forgotten_external" ),
'toggles' => array(
'off' => array( '' ),
'redirect' => array( 'right_to_be_forgotten_link' ),
'on' => array(),
)
) ) );
$form->add( new \IPS\Helpers\Form\Url( 'right_to_be_forgotten_link', \IPS\Settings::i()->right_to_be_forgotten_link, FALSE, array(), NULL, NULL, NULL, 'right_to_be_forgotten_link' ) );
if( $values = $form->values() )
{
$form->saveAsSettings();
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=members&controller=privacy' ), 'saved' );
}
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'settings');
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global', 'core' )->block( 'title', (string) $form );
}
}
@@ -49,6 +49,8 @@ class _refersettings extends \IPS\Dispatcher\Controller
\IPS\Session::i()->log( 'acplog__referrals_disabled' );
}
/* update the essential cookie name list */
unset( \IPS\Data\Store::i()->essentialCookieNames );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal('app=core&module=membersettings&controller=referrals') );
}
@@ -37,19 +37,7 @@ class _pmstats extends \IPS\Dispatcher\Controller
/* Check permission */
\IPS\Dispatcher::i()->checkAcpPermission( 'messages_manage', 'core', 'members' );
$chart = new \IPS\Helpers\Chart\Database( \IPS\Http\Url::internal( 'app=core&module=messengerstats&controller=pmstats' ), 'core_message_posts', 'msg_date', '', array(
'isStacked' => TRUE,
'backgroundColor' => '#ffffff',
'colors' => array( '#10967e', '#ea7963' ),
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4
) );
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack('new_conversations'), 'number', 'SUM(msg_is_first_post)' );
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack('mt_replies'), 'number', '( count(*) - SUM(msg_is_first_post) )' );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('stats_messages_title');
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart' );
$chart = \IPS\core\Statistics\Chart::loadFromExtension( 'core', 'Conversations' )->getChart( \IPS\Http\Url::internal( 'app=core&module=messengerstats&controller=pmstats' ) );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_messengerstats_pmstats');
\IPS\Output::i()->output = (string) $chart;
}
@@ -65,10 +65,15 @@ class _spam extends \IPS\Dispatcher\Controller
$tabs['flagging'] = 'spamprevention_flagging';
$tabs['service'] = 'enhancements__core_SpamMonitoring';
if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'moderation', 'qanda_manage' ) and \in_array( \IPS\Login::registrationType(), array( 'normal', 'full' ) ) )
if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'moderation', 'qanda_manage' ) )
{
$tabs['qanda'] = 'qanda_settings';
}
if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'moderation', 'geolocation' ) )
{
$tabs['geolocation'] = 'geolocation_settings';
}
/* Add a button for logs */
if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'moderation', 'spam_service_log' ) )
@@ -220,7 +225,7 @@ class _spam extends \IPS\Dispatcher\Controller
}
}
$form->add( new \IPS\Helpers\Form\YesNo( 'spam_service_enabled', 0, FALSE, array( 'disabled' => $disabled, 'togglesOn' => array( 'spam_service_send_to_ips', 'spam_service_action_0', 'spam_service_action_1', 'spam_service_action_2', 'spam_service_action_3', 'spam_service_action_4' ) ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'spam_service_enabled', 0, FALSE, array( 'disabled' => $disabled, 'togglesOn' => array( 'spam_service_send_to_ips', 'spam_service_action_0', 'spam_service_action_1', 'spam_service_action_2', 'spam_service_action_3', 'spam_service_action_4', 'spam_service_disposable' ) ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'spam_service_send_to_ips', 0, FALSE, array( 'disabled' => $disabled ), NULL, NULL, NULL, 'spam_service_send_to_ips' ) );
$form->add( new \IPS\Helpers\Form\Select( 'spam_service_action_1', \IPS\Settings::i()->spam_service_action_1, FALSE, array( 'disabled' => $disabled, 'options' => $actions, 'toggles' => array( '5' => array( 'spam_service_action_1_num' ) ) ), NULL, NULL, NULL, 'spam_service_action_1' ) );
$form->add( new \IPS\Helpers\Form\Number( 'spam_service_action_1_num', ( isset( $days[1] ) ? $days[1] : -1 ), FALSE, array( 'unlimited' => -1, 'unlimitedLang' => 'spam_service_action_unlimited_days'), NULL, NULL, \IPS\Member::loggedIn()->language()->addToStack('days'), 'spam_service_action_1_num' ) );
@@ -230,20 +235,23 @@ class _spam extends \IPS\Dispatcher\Controller
$form->add( new \IPS\Helpers\Form\Number( 'spam_service_action_3_num', ( isset( $days[3] ) ? $days[3] : -1 ), FALSE, array( 'unlimited' => -1, 'unlimitedLang' => 'spam_service_action_unlimited_days'), NULL, NULL, \IPS\Member::loggedIn()->language()->addToStack('days'), 'spam_service_action_3_num' ) );
$form->add( new \IPS\Helpers\Form\Select( 'spam_service_action_4', \IPS\Settings::i()->spam_service_action_4, FALSE, array( 'disabled' => $disabled, 'options' => $actions, 'toggles' => array( '5' => array( 'spam_service_action_4_num' ) ) ), NULL, NULL, NULL, 'spam_service_action_4' ) );
$form->add( new \IPS\Helpers\Form\Number( 'spam_service_action_4_num', ( isset( $days[4] ) ? $days[4] : -1 ), FALSE, array( 'unlimited' => -1, 'unlimitedLang' => 'spam_service_action_unlimited_days'), NULL, NULL, \IPS\Member::loggedIn()->language()->addToStack('days'), 'spam_service_action_4_num' ) );
$form->add( new \IPS\Helpers\Form\Select( 'spam_service_disposable', \IPS\Settings::i()->spam_service_disposable, FALSE, array( 'disabled' => $disabled, 'options' => $actions, 'toggles' => array( '5' => array( 'spam_service_disposable_num' ) ) ), NULL, NULL, NULL, 'spam_service_disposable' ) );
$form->add( new \IPS\Helpers\Form\Number( 'spam_service_disposable_num', ( isset( $days['disposable'] ) ? $days['disposable'] : -1 ), FALSE, array( 'unlimited' => -1, 'unlimitedLang' => 'spam_service_action_unlimited_days'), NULL, NULL, \IPS\Member::loggedIn()->language()->addToStack('days'), 'spam_service_disposable_num' ) );
$form->add( new \IPS\Helpers\Form\Select( 'spam_service_action_0', \IPS\Settings::i()->spam_service_action_0, FALSE, array( 'disabled' => $disabled, 'options' => $actions ), NULL, NULL, NULL, 'spam_service_action_0' ) );
if ( $values = $form->values() )
{
$values['spam_service_days'] = array();
foreach( array( 1, 2, 3, 4 ) as $num )
foreach( array( 'action_1', 'action_2', 'action_3', 'action_4', 'disposable' ) as $num )
{
if ( $values['spam_service_action_' . $num ] == 5 )
if ( $values['spam_service_' . $num ] == 5 )
{
$values['spam_service_days'][ $num ] = $values['spam_service_action_' . $num . '_num' ];
$values['spam_service_days'][ str_replace( 'action_', '', $num ) ] = $values['spam_service_' . $num . '_num' ];
}
unset( $values['spam_service_action_' . $num . '_num' ] );
unset( $values['spam_service_' . $num . '_num' ] );
}
$values['spam_service_days'] = json_encode( $values['spam_service_days'] );
@@ -318,7 +326,7 @@ class _spam extends \IPS\Dispatcher\Controller
return $return;
};
return (string) $table;
return \IPS\Theme::i()->getTemplate( 'spam' )->spamQandASettings( $table );
}
/**
@@ -596,7 +604,7 @@ class _spam extends \IPS\Dispatcher\Controller
/* Build form */
$form = new \IPS\Helpers\Form();
$form->add( new \IPS\Helpers\Form\Select( 'whitelist_type', $current['whitelist_type'], TRUE,
$form->add( new \IPS\Helpers\Form\Select( 'whitelist_type', $current ? $current['whitelist_type'] : NULL, TRUE,
array(
'options' => array(
'ip' => 'spam_whitelist_ip_select',
@@ -608,14 +616,14 @@ class _spam extends \IPS\Dispatcher\Controller
)
) ) );
$form->add( new \IPS\Helpers\Form\Text( 'whitelist_ip_content', $current['whitelist_content'], TRUE, array(), NULL, NULL, NULL, 'whitelist_ip_content' ) );
$form->add( new \IPS\Helpers\Form\Text( 'whitelist_domain_content', $current['whitelist_content'], TRUE, array( 'placeholder' => 'mycompany.com' ), function( $value ) {
$form->add( new \IPS\Helpers\Form\Text( 'whitelist_ip_content', $current ? $current['whitelist_content'] : NULL, TRUE, array(), NULL, NULL, NULL, 'whitelist_ip_content' ) );
$form->add( new \IPS\Helpers\Form\Text( 'whitelist_domain_content', $current ? $current['whitelist_content'] : NULL, TRUE, array( 'placeholder' => 'mycompany.com' ), function( $value ) {
if( isset( $value ) AND mb_stripos( $value, '@' ) )
{
throw new \DomainException( 'whitelist_domain_email_detected' );
}
}, NULL, NULL, 'whitelist_domain_content' ) );
$form->add( new \IPS\Helpers\Form\Text( 'whitelist_reason', $current['whitelist_reason'] ) );
$form->add( new \IPS\Helpers\Form\Text( 'whitelist_reason', $current ? $current['whitelist_reason'] : NULL ) );
/* Handle submissions */
if ( $values = $form->values() )
@@ -669,4 +677,69 @@ class _spam extends \IPS\Dispatcher\Controller
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=moderation&controller=spam&do=whitelist' ) );
}
/**
* Show GeoLocation settings
*
* @return string HTML to display
*/
protected function _manageGeolocation()
{
$licenseData = \IPS\IPS::licenseKey();
/* Build Form */
$settings = \IPS\Settings::i()->spam_geo_settings ? json_decode( \IPS\Settings::i()->spam_geo_settings, true ) : array();
$form = new \IPS\Helpers\Form;
$matrix = new \IPS\Helpers\Form\Matrix;
if( !$licenseData or !isset( $licenseData['products']['spam'] ) or !$licenseData['products']['spam'] or ( !$licenseData['cloud'] AND strtotime( $licenseData['expires'] ) < time() ) )
{
if( !\IPS\Settings::i()->ipb_reg_number )
{
\IPS\Member::loggedIn()->language()->words['spam_service_enabled_desc'] = \IPS\Member::loggedIn()->language()->addToStack( '__nulling_null_alert' ); //Changing error message to custom
}
else
{
\IPS\Member::loggedIn()->language()->words['spam_service_enabled_desc'] = \IPS\Member::loggedIn()->language()->addToStack( '__nulling_null_alert' ); //Changing error message to custom
}
}
$matrix->columns = array(
'country' => function( $key, $value, $data ) {
$options = array();
foreach( \IPS\GeoLocation::$countries AS $country )
{
$options[ $country ] = \IPS\Member::loggedIn()->language()->addToStack( 'country-' . $country );
}
return new \IPS\Helpers\Form\Select( $key, $value, TRUE, array( 'options' => $options ) );
},
'action' => function( $key, $value, $data ) {
return new \IPS\Helpers\Form\Select( $key, $value, TRUE, array( 'options' => array( 'moderate' => 'spam_service_act_2', 'block' => 'spam_service_act_4' ) ) );
}
);
$matrix->rows = array();
foreach( $settings AS $country => $action )
{
$matrix->rows[] = array(
'country' => $country,
'action' => $action
);
}
$form->addMatrix( 'geolocation', $matrix );
if ( $values = $form->values() )
{
$save = array();
foreach( $values['geolocation'] AS $key => $value )
{
$save[ $value['country'] ] = $value['action'];
}
\IPS\Settings::i()->changeValues( array( 'spam_geo_settings' => json_encode( $save ) ) );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=moderation&controller=spam&tab=geolocation" ), 'saved' );
}
return \IPS\Theme::i()->getTemplate( 'spam' )->spamGeoSettings( $form );
}
}
@@ -0,0 +1,99 @@
<?php
/**
* @brief mycharts
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @since 09 Dec 2022
*/
namespace IPS\core\modules\admin\overview;
use IPS\core\Statistics\Chart;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* mycharts
*/
class _mycharts extends \IPS\Dispatcher\Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* @brief Allow MySQL RW separation for efficiency
*/
public static $allowRWSeparation = TRUE;
/**
* Manage
*
* @return void
*/
protected function manage()
{
$charts = [];
foreach( Chart::getChartsForMember( \IPS\Http\Url::internal( "app=core&module=overview&controller=mycharts" ), TRUE ) AS $id )
{
$charts[] = $id;
}
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_overview_mycharts');
if ( !\count( $charts ) )
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('stats')->mychartsEmpty();
}
else
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('stats')->mycharts( $charts );
}
}
/**
* Get chart
*
* @return void
*/
public function getChart()
{
if ( !isset( \IPS\Request::i()->chartId ) )
{
if ( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->output = '';
}
else
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=overview&controller=mycharts" ) );
}
}
try
{
$chart = Chart::constructMemberChartFromData( \IPS\Request::i()->chartId, \IPS\Http\Url::internal( "app=core&module=overview&controller=mycharts&do=getChart&chartId=" . \IPS\Request::i()->chartId ) );
\IPS\Output::i()->output = (string) $chart;
}
catch( \Throwable )
{
if ( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->output = '';
}
else
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=overview&controller=mycharts" ) );
}
}
}
}
@@ -115,7 +115,7 @@ class _onboard extends \IPS\Dispatcher\Controller
$values[ 'site_address' ] = json_encode( $values[ 'site_address' ] );
$values[ 'icons_favicon' ] = (string) $values[ 'icons_favicon' ];
$values=\IPS\core\modules\admin\customization\icons::processApplicationIcon( $values );
$values=\IPS\core\modules\admin\settings\webapp::processApplicationIcon( $values );
$values=\IPS\core\modules\admin\settings\terms::processForm( $values );
/* If a logo was uploaded, do stuff with it... */
@@ -110,6 +110,8 @@ class _contactus extends \IPS\Dispatcher\Controller
{
$form->add( $field );
}
$form->add( new \IPS\Helpers\Form\YesNo( 'contact_email_verify', \IPS\Settings::i()->contact_email_verify, TRUE ) );
return $form;
}
@@ -196,7 +196,12 @@ class _email extends \IPS\Dispatcher\Controller
}
catch ( \Exception $exception )
{
$messageHtml = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->message( \get_class( $exception ) . ": " . $exception->getMessage() . " (" . $exception->getCode() . ")", 'error' );
$content = $exception->getMessage();
if ( !( $exception instanceof \IPS\Email\Outgoing\Exception ) )
{
$content = \get_class( $exception ) . ": " . $exception->getMessage() . " (" . $exception->getCode() . ")";
}
$messageHtml = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->message( $content, 'error' );
}
}
@@ -121,7 +121,7 @@ class _posting extends \IPS\Dispatcher\Controller
$form->addHeader('posting_images');
$form->add( new \IPS\Helpers\Form\WidthHeight( 'attachment_resample_size', \IPS\Settings::i()->attachment_resample_size ? explode( 'x', \IPS\Settings::i()->attachment_resample_size ) : array( 0, 0 ), FALSE, array( 'resizableDiv' => FALSE, 'unlimited' => array( 0, 0 ) ), NULL, NULL, NULL, 'attachment_resample_size' ) );
$current = ( isset( \IPS\Settings::i()->attachment_image_size ) ) ? explode( 'x', \IPS\Settings::i()->attachment_image_size ) : array( 1000, 750 );
$form->add( new \IPS\Helpers\Form\WidthHeight( 'attachment_image_size', $current, FALSE, array( 'resizableDiv' => FALSE, 'unlimited' => array( 0, 0 ) ), NULL, NULL, NULL, 'attachment_image_size' ) );
$form->add( new \IPS\Helpers\Form\WidthHeight( 'attachment_image_size', $current, FALSE, array( 'resizableDiv' => FALSE, 'unlimited' => array( 0, 0 ) ), function( $value ){ if( $value[0] > 4800 OR $value[1] > 4800 ) { throw new \InvalidArgumentException('form_image_too_large'); } }, NULL, NULL, 'attachment_image_size' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'editor_embeds', \IPS\Settings::i()->editor_embeds, FALSE, array( 'togglesOn' => array( 'max_video_width', 'max_internalembed_width' ) ) ) );
$form->add( new \IPS\Helpers\Form\Number( 'max_video_width', \IPS\Settings::i()->max_video_width, FALSE, array( 'unlimited' => 0 ), NULL, NULL, 'px', 'max_video_width' ) );
$form->add( new \IPS\Helpers\Form\Number( 'max_internalembed_width', \IPS\Settings::i()->max_internalembed_width, FALSE, array( 'unlimited' => 0 ), NULL, NULL, 'px', 'max_internalembed_width' ) );
@@ -478,10 +478,10 @@ class _posting extends \IPS\Dispatcher\Controller
$form = new \IPS\Helpers\Form();
$form->add( new \IPS\Helpers\Form\YesNo( 'tags_enabled', \IPS\Settings::i()->tags_enabled, FALSE, array( 'togglesOn' => array( 'tags_can_prefix', 'tags_open_system', 'tags_predefined', 'tags_force_lower', 'tags_min', 'tags_max', 'tags_len_min', 'tags_len_max', 'tags_clean' ) ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'tags_enabled', \IPS\Settings::i()->tags_enabled, FALSE, array( 'togglesOn' => array( 'tags_can_prefix', 'tags_open_system', 'tags_predefined', 'tags_force_lower', 'tags_min', 'tags_max', 'tags_len_min', 'tags_len_max', 'tags_clean', 'tags_alphabetical' ) ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'tags_can_prefix', \IPS\Settings::i()->tags_can_prefix, FALSE, array(), NULL, NULL, NULL, 'tags_can_prefix' ) );
$form->add( new \IPS\Helpers\Form\Radio( 'tags_open_system', \IPS\Settings::i()->tags_open_system, FALSE, array( 'options' => array( 1 => 'tags_open_system_open', 0 => 'tags_open_system_closed' ) ), NULL, NULL, NULL, 'tags_open_system' ) );
$form->add( new \IPS\Helpers\Form\Text( 'tags_predefined', \IPS\Settings::i()->tags_predefined, FALSE, array( 'autocomplete' => array( 'unique' => TRUE ) ), NULL, NULL, NULL, 'tags_predefined' ) );
$form->add( new \IPS\Helpers\Form\Text( 'tags_predefined', \IPS\Settings::i()->tags_predefined, FALSE, array( 'autocomplete' => array( 'unique' => TRUE, 'alphabetical' => \IPS\Settings::i()->tags_alphabetical ) ), NULL, NULL, NULL, 'tags_predefined' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'tags_force_lower', \IPS\Settings::i()->tags_force_lower, FALSE, array(), NULL, NULL, NULL, 'tags_force_lower' ) );
$form->add( new \IPS\Helpers\Form\Number( 'tags_min', \IPS\Settings::i()->tags_min, FALSE, array( 'unlimited' => 0, 'unlimitedLang' => 'tags_min_none', 'unlimitedToggles' => array( 'tags_min_req' ), 'unlimitedToggleOn' => FALSE ), NULL, NULL, NULL, 'tags_min' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'tags_min_req', \IPS\Settings::i()->tags_min_req, FALSE, array(), NULL, NULL, NULL, 'tags_min_req' ) );
@@ -489,6 +489,7 @@ class _posting extends \IPS\Dispatcher\Controller
$form->add( new \IPS\Helpers\Form\Number( 'tags_len_min', \IPS\Settings::i()->tags_len_min, FALSE, array( 'unlimited' => 0 ), NULL, NULL, \IPS\Member::loggedIn()->language()->addToStack( 'characters' ), 'tags_len_min' ) );
$form->add( new \IPS\Helpers\Form\Number( 'tags_len_max', \IPS\Settings::i()->tags_len_max, FALSE, array( 'unlimited' => 0 ), NULL, NULL, \IPS\Member::loggedIn()->language()->addToStack( 'characters' ), 'tags_len_max' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'tags_clean', \IPS\Settings::i()->tags_clean, FALSE, array(), NULL, NULL, NULL, 'tags_clean' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'tags_alphabetical', \IPS\Settings::i()->tags_alphabetical, FALSE, array(), NULL, NULL, NULL, 'tags_alphabetical' ) );
if ( ! \IPS\Settings::i()->tags_open_system and ! \IPS\Settings::i()->tags_predefined )
{
@@ -90,7 +90,7 @@ class _terms extends \IPS\Dispatcher\Controller
*/
public static function processForm( $values )
{
foreach ( array( 'gl_guidelines' => 'guidelines_value', 'privacy_text' => 'privacy_text_value', 'reg_rules' => 'reg_rules_value', 'guest_terms_bar_text' => 'guest_terms_bar_text_value' ) as $k => $v )
foreach ( array( 'gl_guidelines' => 'guidelines_value', 'privacy_text' => 'privacy_text_value', 'reg_rules' => 'reg_rules_value', 'guest_terms_bar_text' => 'guest_terms_bar_text_value', 'cookie_3rdpartynotice' => 'cookie_3rdpartynotice_value' ) as $k => $v )
{
/* Guest terms bar text has special replacements that need to be swapped to sprintf parameters */
if ( $k == 'guest_terms_bar_text' and $values['guest_terms_bar'] )
@@ -105,6 +105,9 @@ class _terms extends \IPS\Dispatcher\Controller
unset( $values[ $k ] );
}
/* Update the essential cookie name list */
unset( \IPS\Data\Store::i()->essentialCookieNames );
return $values;
}
@@ -157,6 +160,9 @@ class _terms extends \IPS\Dispatcher\Controller
$form->addHeader( 'guest_terms_options' );
$form->add( new \IPS\Helpers\Form\YesNo( 'guest_terms_bar', \IPS\Settings::i()->guest_terms_bar, FALSE, array( 'togglesOn' => array( 'guest_terms_bar_text' ) ) ) );
$form->add( new \IPS\Helpers\Form\Translatable( 'guest_terms_bar_text', NULL, FALSE, array( 'app' => 'core', 'key' => 'guest_terms_bar_text_value', 'sprintf' => array( '{terms}', '{privacy}', '{guidelines}', '{cookies}' ) ), NULL, NULL, NULL, 'guest_terms_bar_text' ) );
$form->addHeader( 'terms_cookies' );
$form->add( new \IPS\Helpers\Form\Translatable( 'cookie_3rdpartynotice', NULL, FALSE, array( 'app' => 'core', 'key' => 'cookie_3rdpartynotice_value', 'editor' => array( 'app' => 'core', 'key' => 'Admin', 'autoSaveKey' => '3rdPartyCookies', 'attachIds' => array( NULL, NULL, 'privacy_text' ) ) ) ));
}
/**
@@ -0,0 +1,483 @@
<?php
/**
* @brief webapp
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @since 28 Mar 2023
*/
namespace IPS\core\modules\admin\settings;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* webapp
*/
class _webapp extends \IPS\Dispatcher\Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'webapp_manage' );
parent::execute();
}
/**
* ...
*
* @return void
*/
protected function manage()
{
$form = new \IPS\Helpers\Form;
$form->addTab( 'webapp_tab_icons' );
/* Homescreen icons - we accept one upload and create the images we need */
$homeScreen = json_decode( \IPS\Settings::i()->icons_homescreen, TRUE ) ?? array();
$form->add( new \IPS\Helpers\Form\Upload( 'icons_homescreen', ( isset( $homeScreen['original'] ) ) ? \IPS\File::get( 'core_Icons', $homeScreen['original'] ) : NULL, FALSE, array( 'image' => true, 'storageExtension' => 'core_Icons' ) ) );
$homeScreenMaskable = json_decode( \IPS\Settings::i()->icons_homescreen_maskable, TRUE ) ?? array();
$form->add( new \IPS\Helpers\Form\Upload( 'icons_homescreen_maskable', ( isset( $homeScreenMaskable['original'] ) ) ? \IPS\File::get( 'core_Icons', $homeScreenMaskable['original'] ) : NULL, FALSE, array( 'image' => true, 'storageExtension' => 'core_Icons', 'allowedFileTypes' => ['png', 'webp' ] ) ) );
/* Apple startup screen logo - we accept one upload and create the images we need */
$apple = json_decode( \IPS\Settings::i()->icons_apple_startup, TRUE ) ?? array();
$form->add( new \IPS\Helpers\Form\Upload( 'icons_apple_startup', ( isset( $apple['original'] ) ) ? \IPS\File::get( 'core_Icons', $apple['original'] ) : NULL, FALSE, array( 'image' => true, 'storageExtension' => 'core_Icons' ) ) );
/* Safari pinned tabs icon and highlight color */
$form->add( new \IPS\Helpers\Form\Upload( 'icons_mask_icon', \IPS\Settings::i()->icons_mask_icon ? \IPS\File::get( 'core_Icons', \IPS\Settings::i()->icons_mask_icon ) : NULL, FALSE, array( 'allowedFileTypes' => array( 'svg' ), 'storageExtension' => 'core_Icons', 'obscure' => FALSE ) ) );
$form->add( new \IPS\Helpers\Form\Color( 'icons_mask_color', \IPS\Settings::i()->icons_mask_color, FALSE ) );
$form->addTab( 'webapp_tab_manifest' );
/* And finally, additional manifest and livetile details */
$manifestDetails = json_decode( \IPS\Settings::i()->manifest_details, TRUE );
$form->add( new \IPS\Helpers\Form\YesNo( 'configure_manifest', \count( $manifestDetails ) > 0, FALSE, array(
'togglesOn' => array( 'manifest_shortname', 'manifest_fullname', 'manifest_description', 'manifest_defaultapp', 'manifest_themecolor', 'manifest_bgcolor', 'manifest_display', 'manifest_custom_url_toggle' ),
) ) );
$form->add( new \IPS\Helpers\Form\Text( 'manifest_shortname', ( isset( $manifestDetails['short_name'] ) ) ? $manifestDetails['short_name'] : '', FALSE, array(), NULL, NULL, NULL, 'manifest_shortname' ) );
$form->add( new \IPS\Helpers\Form\Text( 'manifest_fullname', ( isset( $manifestDetails['name'] ) ) ? $manifestDetails['name'] : '', FALSE, array(), NULL, NULL, NULL, 'manifest_fullname' ) );
$form->add( new \IPS\Helpers\Form\TextArea( 'manifest_description', ( isset( $manifestDetails['description'] ) ) ? $manifestDetails['description'] : '', FALSE, array(), NULL, NULL, NULL, 'manifest_description' ) );
$formStartUrl = '';
if( isset( $manifestDetails['start_url'] ) )
{
$formStartUrl = str_replace( 'index.php?/', '', \IPS\Http\Url\Friendly::fixComponentPath( $manifestDetails['start_url'] ) );
}
$form->add( new \IPS\Helpers\Form\YesNo( 'manifest_custom_url_toggle', ( isset( $manifestDetails['start_url'] ) and empty( $formStartUrl ) ? FALSE : TRUE ), FALSE, array(
'togglesOn' => array( 'manifest_short_url' ),
), NULL, NULL, NULL, 'manifest_custom_url_toggle' ) );
$form->add( new \IPS\Helpers\Form\Text( 'manifest_short_url', $formStartUrl, FALSE, array(), function( $val )
{
if ( $val and \IPS\Request::i()->manifest_custom_url_toggle_checkbox )
{
if ( mb_substr( $val, -1 ) !== '/' )
{
$val .= '/';
}
$response = \IPS\Http\Url::external( \IPS\Http\Url::baseUrl() . ( \IPS\Settings::i()->htaccess_mod_rewrite ? $val : 'index.php?/' . $val ) )->request( NULL, NULL, FALSE )->get();
if ( $response->httpResponseCode != 200 and $response->httpResponseCode != 303 and ( \IPS\Settings::i()->site_online OR $response->httpResponseCode != 503 ) )
{
throw new \LogicException( 'pwa_start_url_incorrect' );
}
}
}, \IPS\Http\Url::baseUrl() . ( !\IPS\Settings::i()->htaccess_mod_rewrite ? 'index.php?/' : '' ), NULL, 'manifest_short_url' ) );
$form->add( new \IPS\Helpers\Form\Color( 'manifest_themecolor', ( isset( $manifestDetails['theme_color'] ) ) ? $manifestDetails['theme_color'] : NULL, FALSE, array(), NULL, NULL, NULL, 'manifest_themecolor' ) );
$form->add( new \IPS\Helpers\Form\Color( 'manifest_bgcolor', ( isset( $manifestDetails['background_color'] ) ) ? $manifestDetails['background_color'] : NULL, FALSE, array(), NULL, NULL, NULL, 'manifest_bgcolor' ) );
$form->add( new \IPS\Helpers\Form\Radio( 'manifest_display', ( isset( $manifestDetails['display'] ) ) ? $manifestDetails['display'] : 'standalone', FALSE, array( 'options' => array( 'fullscreen' => 'manifest_fullscreen', 'standalone' => 'manifest_standalone', 'minimal-ui' => 'manifest_minimalui', 'browser' => 'manifest_browser' ) ), NULL, NULL, NULL, 'manifest_display' ) );
if( $values = $form->values() )
{
$path = \IPS\Http\Url::createFromString( \IPS\Http\Url::baseUrl() )->data[ \IPS\Http\Url::COMPONENT_PATH ];
$startUrl = $path ?? '';
if ( $values['manifest_custom_url_toggle'] !== FALSE and ! empty( $values['manifest_short_url'] ) )
{
$startUrl = '/' . trim( $values['manifest_short_url'], '/' ) . '/';
if( !empty( $path ) )
{
$startUrl = '/' . trim( $path . ( !\IPS\Settings::i()->htaccess_mod_rewrite ? 'index.php?/' : '' ) . ltrim( $values['manifest_short_url'], '/' ), '/' ) . '/';
}
}
/* Homescreen icon is the hardest part, as we need to generate different sizes.. */
$values = static::processApplicationIcon( $values, $homeScreen, $homeScreenMaskable );
/* The start screen requires more different sizes so we'll rebuild that after submit */
$rebuildStartScreen = FALSE;
if( $values['icons_apple_startup'] AND ( !isset( $apple['original'] ) OR !$apple['original'] OR (string) $values['icons_apple_startup'] != $apple['original'] ) )
{
$rebuildStartScreen = TRUE;
}
if( ( !isset( $manifestDetails['background_color'] ) AND $values['manifest_bgcolor'] ) OR ( isset( $manifestDetails['background_color'] ) AND !$values['manifest_bgcolor'] ) OR ( isset( $manifestDetails['background_color'] ) AND $values['manifest_bgcolor'] != $manifestDetails['background_color'] ) )
{
if( $values['icons_apple_startup'] )
{
$rebuildStartScreen = TRUE;
}
}
$values = static::processAppleStartupScreen( $values, $apple );
/* We need the string value of this uploaded file as well */
$values['icons_mask_icon'] = (string) $values['icons_mask_icon'];
/* Finally, handle the manifest details */
$values['manifest_details'] = array();
if( $values['configure_manifest'] )
{
$values['manifest_details']['short_name'] = $values['manifest_shortname'];
$values['manifest_details']['start_url'] = $startUrl;
$values['manifest_details']['name'] = $values['manifest_fullname'];
$values['manifest_details']['description'] = $values['manifest_description'];
$values['manifest_details']['theme_color'] = $values['manifest_themecolor'];
$values['manifest_details']['background_color'] = $values['manifest_bgcolor'];
$values['manifest_details']['display'] = $values['manifest_display'];
}
unset( $values['configure_manifest'], $values['manifest_shortname'], $values['manifest_fullname'], $values['manifest_description'], $values['manifest_display'], $values['manifest_bgcolor'], $values['manifest_themecolor'], $values['manifest_custom_url_toggle'], $values['manifest_short_url'] );
$values['manifest_details'] = json_encode( $values['manifest_details'] );
/* Save the settings */
$form->saveAsSettings( $values );
/* Clear guest page caches */
\IPS\Data\Cache::i()->clearAll();
/* Clear manifest and ie browser data stores */
unset( \IPS\Data\Store::i()->manifest, \IPS\Data\Store::i()->iebrowserconfig );
/* And log */
\IPS\Session::i()->log( 'acplogs__webapp' );
/* And Redirect */
if( $rebuildStartScreen === TRUE )
{
\IPS\Output::i()->redirect( $this->url->setQueryString( 'do', 'buildStartupScreenImages' ), 'saved' );
}
else
{
\IPS\Output::i()->redirect( $this->url, 'saved' );
}
}
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_settings_webapp');
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'global' )->block( 'menu__core_settings_webapp', $form );
}
/**
* Process application icon
*
* @param array $values Values from form submission
* @param array $homeScreen Existing values, if any
* @param array $homeScreenMaskable Existing values, if any
* @return array
*/
public static function processApplicationIcon( $values, $homeScreen = array(), $homeScreenMaskable = array() )
{
if( ( isset( $values['icons_homescreen'] ) AND $values['icons_homescreen'] ) or ( isset( $values['icons_homescreen_maskable' ] ) AND $values['icons_homescreen_maskable'] ) )
{
foreach( [ 'icons_homescreen', 'icons_homescreen_maskable' ] as $type )
{
$setting = [];
if( isset( $values[ $type ] ) AND $values[ $type ] )
{
$sizes = array(
'android-chrome-36x36' => array(36, 36),
'android-chrome-48x48' => array(48, 48),
'android-chrome-72x72' => array(72, 72),
'android-chrome-96x96' => array(96, 96),
'android-chrome-144x144' => array(144, 144),
'android-chrome-192x192' => array(192, 192),
'android-chrome-256x256' => array(256, 256),
'android-chrome-384x384' => array(384, 384),
'android-chrome-512x512' => array(512, 512),
'msapplication-square70x70logo' => array(128, 128),
'msapplication-TileImage' => array(144, 144),
'msapplication-square150x150logo' => array(270, 270),
'msapplication-wide310x150logo' => array(558, 558),
'msapplication-square310x310logo' => array(558, 270),
'apple-touch-icon-57x57' => array(57, 57),
'apple-touch-icon-60x60' => array(60, 60),
'apple-touch-icon-72x72' => array(72, 72),
'apple-touch-icon-76x76' => array(76, 76),
'apple-touch-icon-114x114' => array(114, 114),
'apple-touch-icon-120x120' => array(120, 120),
'apple-touch-icon-144x144' => array(144, 144),
'apple-touch-icon-152x152' => array(152, 152),
'apple-touch-icon-180x180' => array(180, 180),
);
$setting = array('original' => (string)$values[ $type ]);
foreach ( $sizes as $filename => $_sizes )
{
if ( $type == 'icons_homescreen_maskable' )
{
$filename .= "-masked";
if ( ! \mb_strstr( $filename, 'android-chrome' ) )
{
continue;
}
}
try
{
$image = \IPS\Image::create( $values[ $type ]->contents() );
if ( $image::exifSupported() )
{
$image->setExifData( $values[ $type ]->contents() );
}
$image->crop( $_sizes[0], $_sizes[1] );
$setting[$filename] = array(
'url' => (string)\IPS\File::create( 'core_Icons', $filename . '.png', (string)$image, NULL, TRUE, NULL, FALSE ),
'width' => $image->width,
'height' => $image->height
);
}
catch ( \Exception $e )
{
}
}
}
$values[ $type ] = json_encode( $setting );
}
}
else
{
/* Delete any images that may already exist */
foreach( $homeScreen as $key => $image )
{
try
{
\IPS\File::get( 'core_Icons', ( $key == 'original' ) ? $image : $image['url'] )->delete();
}
catch( \Exception $e ){}
}
foreach( $homeScreenMaskable as $key => $image )
{
try
{
\IPS\File::get( 'core_Icons', ( $key == 'original' ) ? $image : $image['url'] )->delete();
}
catch( \Exception $e ){}
}
$values['icons_homescreen'] = '';
}
return $values;
}
/**
* @brief Minimum padding on either side of startup image (in px)
*/
const MINIMUM_STARTUP_IMAGE_PADDING = 50;
/**
* Process Apple startup screen images
*
* @param array $values Values from form submission
* @param array $apple Existing values, if any
* @return array
*/
public static function processAppleStartupScreen( $values, $apple = array() )
{
if( $values['icons_apple_startup'] )
{
$values['icons_apple_startup'] = json_encode( array( 'original' => (string) $values['icons_apple_startup'] ) );
}
else
{
/* Delete any images that may already exist */
foreach( $apple as $key => $image )
{
try
{
\IPS\File::get( 'core_Icons', ( $key == 'original' ) ? $image : $image['url'] )->delete();
}
catch( \Exception $e ){}
}
$values['icons_apple_startup'] = '';
}
return $values;
}
/**
* Process Apple startup screen images
*
* @param array $values Values from form submission
* @param array $apple Existing values, if any
* @return array
*/
public function buildStartupScreenImages()
{
$self = $this;
$multiRedirect = new \IPS\Helpers\MultipleRedirect(
$this->url->setQueryString('do', 'buildStartupScreenImages'),
function( $data )
{
/* Get the necessary data */
$manifestDetails = json_decode( \IPS\Settings::i()->manifest_details, TRUE );
$setting = json_decode( \IPS\Settings::i()->icons_apple_startup, TRUE ) ?? array();
if( isset( $setting['original'] ) )
{
$sizes = array(
'apple-startup-1136x640' => array( 320, 568, 2, 'landscape' ),
'apple-startup-2436x1125' => array( 375, 812, 3, 'landscape' ),
'apple-startup-1792x828' => array( 414, 896, 2, 'landscape' ),
'apple-startup-828x1792' => array( 414, 896, 2, 'portrait' ),
'apple-startup-1334x750' => array( 375, 667, 2, 'landscape' ),
'apple-startup-1242x2688' => array( 414, 896, 3, 'portrait' ),
'apple-startup-2208x1242' => array( 414, 736, 3, 'landscape' ),
'apple-startup-1125x2436' => array( 375, 812, 3, 'portrait' ),
'apple-startup-1242x2208' => array( 414, 736, 3, 'portrait' ),
'apple-startup-2732x2048' => array( 1024, 1366, 2, 'landscape' ),
'apple-startup-2688x1242' => array( 414, 896, 3, 'landscape' ),
'apple-startup-2224x1668' => array( 834, 1112, 2, 'landscape' ),
'apple-startup-750x1334' => array( 375, 667, 2, 'portrait' ),
'apple-startup-2048x2732' => array( 1024, 1366, 2, 'portrait' ),
'apple-startup-2388x1668' => array( 834, 1194, 2, 'landscape' ),
'apple-startup-1668x2224' => array( 834, 1112, 2, 'portrait' ),
'apple-startup-640x1136' => array( 320, 568, 2, 'portrait' ),
'apple-startup-1668x2388' => array( 834, 1194, 2, 'portrait' ),
'apple-startup-2048x1536' => array( 768, 1024, 2, 'landscape' ),
'apple-startup-1536x2048' => array( 768, 1024, 2, 'portrait' ),
'apple-startup-2360x1640' => array( 1180, 820, 2, 'landscape' ),
'apple-startup-1640x2360' => array( 1180, 820, 2, 'portrait' ),
'apple-startup-2160x1620' => array( 1080, 810, 2, 'landscape' ),
'apple-startup-1620x2160' => array( 1080, 810, 2, 'portrait' ),
'apple-startup-2778x1284' => array( 428, 926, 3, 'landscape' ),
'apple-startup-1284x2778' => array( 428, 926, 3, 'portrait' ),
'apple-startup-2532x1170' => array( 390, 844, 3, 'landscape' ),
'apple-startup-1170x2532' => array( 390, 844, 3, 'portrait' ),
'apple-startup-2340x1080' => array( 360, 780, 3, 'landscape' ),
'apple-startup-1080x2340' => array( 360, 780, 3, 'portrait' ),
);
$file = \IPS\File::get( 'core_Icons', $setting['original'] );
$originalContents = $file->contents();
$backgroundColor = ( isset( $manifestDetails['background_color'] ) ) ? str_replace( '#', '', $manifestDetails['background_color'] ) : 'FFFFFF';
$rgb = array();
if ( \strlen( $backgroundColor ) == 3 )
{
$rgb[] = hexdec( \substr( $backgroundColor, 0, 1 ) . \substr( $backgroundColor, 0, 1 ) ); // R
$rgb[] = hexdec( \substr( $backgroundColor, 1, 1 ) . \substr( $backgroundColor, 1, 1 ) ); // G
$rgb[] = hexdec( \substr( $backgroundColor, 2, 1 ) . \substr( $backgroundColor, 2, 1 ) ); // B
}
else
{
$rgb[] = hexdec( \substr( $backgroundColor, 0, 2 ) ); // R
$rgb[] = hexdec( \substr( $backgroundColor, 2, 2 ) ); // G
$rgb[] = hexdec( \substr( $backgroundColor, 4, 2 ) ); // B
}
$did = 0;
foreach( $sizes as $filename => $_sizes )
{
$did++;
if( isset( $setting[ $filename ] ) )
{
continue;
}
try
{
$width = ( $_sizes[3] == 'landscape' ) ? $_sizes[1] * $_sizes[2] : $_sizes[0] * $_sizes[2];
$height = ( $_sizes[3] == 'portrait' ) ? $_sizes[1] * $_sizes[2] : $_sizes[0] * $_sizes[2];
$logoImage = \IPS\Image::create( $originalContents );
$canvas = \IPS\Image::newImageCanvas( $width, $height, $rgb );
$logoImage->resizeToMax( $width - static::MINIMUM_STARTUP_IMAGE_PADDING, $height - static::MINIMUM_STARTUP_IMAGE_PADDING );
$xPos = ( $width - $logoImage->width ) / 2;
$yPos = ( $height - $logoImage->height ) / 2;
$canvas->impose( $logoImage, $xPos, $yPos );
if( $canvas::exifSupported() )
{
$canvas->setExifData( $originalContents );
}
$setting[ $filename ] = array(
'url' => (string) \IPS\File::create( 'core_Icons', $filename . '.png', (string) $canvas, NULL, TRUE, NULL, FALSE ),
'width' => $canvas->width,
'height' => $canvas->height,
'density' => $_sizes[2],
'orientation' => $_sizes[3],
);
}
catch ( \Exception $e )
{
\IPS\Log::log( $e, 'apple-startup-image' );
}
break;
}
/* Are we done? */
if( $did == \count( $sizes ) )
{
return NULL;
}
\IPS\Settings::i()->changeValues( array( 'icons_apple_startup' => json_encode( $setting ) ) );
}
return array( $data, \IPS\Member::loggedIn()->language()->addToStack('build_start_images_title'), ( $did ) ? round( ( 100 / \count( $sizes ) * $did ), 2 ) : 0 );
},
function() use( $self )
{
\IPS\Output::i()->redirect( $self->url, 'completed' );
}
);
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('build_start_images_title');
\IPS\Output::i()->output = $multiRedirect;
}
}
@@ -114,39 +114,12 @@ class _badges extends \IPS\Dispatcher\Controller
'list' => 'stats_badges_by_group',
'member' => 'stats_badges_by_member',
);
$activeTab = ( isset( \IPS\Request::i()->tab ) and array_key_exists( \IPS\Request::i()->tab, $tabs ) ) ? \IPS\Request::i()->tab : 'type';
\IPS\Request::i()->tab ??= 'type';
$activeTab = ( array_key_exists( \IPS\Request::i()->tab, $tabs ) ) ? \IPS\Request::i()->tab : 'type';
if ( $activeTab === 'type' )
{
$chart = new \IPS\Helpers\Chart\Database( \IPS\Http\Url::internal( 'app=core&module=stats&controller=badges&tab=' . $activeTab ), 'core_member_badges', 'datetime', '', array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'hAxis' => array('gridlines' => array('color' => '#f5f5f5')),
'lineWidth' => 1,
'areaOpacity' => 0.4
),
'AreaChart',
'daily',
array('start' => 0, 'end' => 0),
array(),
$activeTab
);
$chart->title = \IPS\Member::loggedIn()->language()->addToStack( 'stats_badges_title' );
$chart->availableTypes = array('AreaChart', 'ColumnChart', 'BarChart');
$chart->enableHourly = FALSE;
$chart->groupBy = 'badge';
foreach ( \IPS\core\Achievements\Badge::roots() as $badge )
{
$chart->addSeries( $badge->_title, 'number', 'COUNT(*)', TRUE, $badge->id );
}
$chart->title = \IPS\Member::loggedIn()->language()->addToStack( 'stats_badges_title' );
$chart->availableTypes = array('AreaChart', 'ColumnChart', 'BarChart');
$chart->showIntervals = TRUE;
$chart = \IPS\core\Statistics\Chart::loadFromExtension( 'core', 'Badges' )->getChart( \IPS\Http\Url::internal( 'app=core&module=stats&controller=badges&tab=' . $activeTab ) );
}
elseif ( $activeTab === 'member' )
{
@@ -58,49 +58,7 @@ class _deviceusage extends \IPS\Dispatcher\Controller
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('prunesettings') )
);
/* Determine minimum date */
$minimumDate = NULL;
if( \IPS\Settings::i()->stats_device_usage_prune )
{
$minimumDate = \IPS\DateTime::create()->sub( new \DateInterval( 'P' . \IPS\Settings::i()->stats_device_usage_prune . 'D' ) );
}
/* We can't retrieve any stats prior to the new tracking being implemented */
try
{
$oldestLog = \IPS\Db::i()->select( 'MIN(time)', 'core_statistics', array( 'type=?', 'online_users' ) )->first();
if( !$minimumDate OR $oldestLog < $minimumDate->getTimestamp() )
{
$minimumDate = \IPS\DateTime::ts( $oldestLog );
}
}
catch( \UnderflowException $e )
{
/* We have nothing tracked, set minimum date to today */
$minimumDate = \IPS\DateTime::create();
}
$chart = new \IPS\Helpers\Chart\Database( \IPS\Http\Url::internal( 'app=core&module=stats&controller=deviceusage' ), 'core_statistics', 'time', '', array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'colors' => array( '#10967e', '#ea7963', '#de6470', '#6b9dde' ),
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4
), 'AreaChart', 'hourly' );
$chart->where[] = array( 'type=?', 'devices' );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('stats_deviceusage_title');
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart' );
$chart->enableHourly = TRUE;
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack('stats_devices_mobiles'), 'number', 'SUM(value_1)', TRUE );
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack('stats_devices_tablets'), 'number', 'SUM(value_2)', TRUE );
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack('stats_devices_consoles'), 'number', 'SUM(value_3)', TRUE );
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack('stats_devices_desktops'), 'number', 'SUM(value_4)', TRUE );
$chart = \IPS\core\Statistics\Chart::loadFromExtension( 'core', 'DeviceUsage' )->getChart( \IPS\Http\Url::internal( "app=core&module=stats&controller=deviceusage" ) );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_stats_deviceusage');
\IPS\Output::i()->output = (string) $chart;
}
@@ -46,91 +46,14 @@ class _moderators extends \IPS\Dispatcher\Controller
*/
protected function manage()
{
$chart = new \IPS\Helpers\Chart\Database( \IPS\Http\Url::internal( 'app=core&module=stats&controller=moderators'), 'core_moderator_logs', 'ctime', '', array(
'isStacked' => TRUE,
'backgroundColor' => '#ffffff',
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4
), 'LineChart', 'monthly', array( 'start' => 0, 'end' => 0 ), array( 'member_id', 'ctime' ), 'spam' );
$chart->groupBy = 'member_id';
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('stats_moderator_activity_title');
$chart->availableTypes = array( 'LineChart', 'AreaChart', 'ColumnChart', 'BarChart' );
$chart->tableParsers = array(
'ctime' => function( $val )
{
return (string) \IPS\DateTime::ts( $val );
}
);
/* Didn't find any specified? Do the default of the top 50 moderators */
$where = array();
if ( $chart->start instanceof \IPS\DateTime or $chart->end instanceof \IPS\DateTime )
{
$start = $chart->start instanceof \IPS\DateTime ? $chart->start->getTimestamp() : 0;
$end = $chart->end instanceof \IPS\DateTime ? $chart->end->getTimestamp() : time();
$where[] = array( 'ctime BETWEEN ? AND ?', $start, $end );
}
/* Get actual moderators */
$modMember = [];
$modGroup = [];
foreach ( \IPS\Db::i()->select( '*', 'core_moderators' ) as $mod )
{
if ( $mod['type'] == 'g' )
{
$modGroup[] = $mod['id'];
}
else
{
$modMember[] = $mod['id'];
}
}
$query = [];
if ( \count( $modGroup ) )
{
$query[] = \IPS\Db::i()->in( 'member_group_id', $modGroup );
$query[] = \IPS\Db::i()->findInSet( 'mgroup_others', $modGroup );
}
if ( \count( $modMember ) )
{
$query[] = \IPS\Db::i()->in( 'member_id', $modMember );
}
/* I mean $query should never be empty, but lets not wait for a ticket and have to release a patch to find out that it could be... */
if ( \count( $query ) )
{
$where[] = [ 'member_id IN(?)', \IPS\Db::i()->select( 'member_id', 'core_members', implode( ' OR ', $query ) ) ];
}
$topModerators = \IPS\Db::i()->select( 'member_id, COUNT(*) as _mod_count', 'core_moderator_logs', $where, '_mod_count DESC', 50, array( 'member_id' ) );
foreach ( $topModerators as $moderatorRow )
{
$member = \IPS\Member::load( $moderatorRow['member_id'] );
$chart->addSeries(
( $member->member_id ) ? $member->name : \IPS\Member::loggedIn()->language()->addToStack( 'deleted_member' ),
'number',
'COUNT(*)',
TRUE,
$moderatorRow['member_id']
);
}
if ( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->output = (string) $chart;
\IPS\Output::i()->output = (string) \IPS\core\Statistics\Chart::loadFromExtension( 'core', 'Moderators' )->getChart( \IPS\Http\Url::internal( 'app=core&module=stats&controller=moderators' ) );
}
else
{
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_stats_moderators');
\IPS\Output::i()->output = (string) $chart;
\IPS\Output::i()->output = (string) \IPS\core\Statistics\Chart::loadFromExtension( 'core', 'Moderators' )->getChart( \IPS\Http\Url::internal( 'app=core&module=stats&controller=moderators' ) );
}
}
}
@@ -58,56 +58,7 @@ class _onlineusers extends \IPS\Dispatcher\Controller
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('prunesettings') )
);
/* Determine minimum date */
$minimumDate = NULL;
if( \IPS\Settings::i()->stats_online_users_prune )
{
$minimumDate = \IPS\DateTime::create()->sub( new \DateInterval( 'P' . \IPS\Settings::i()->stats_online_users_prune . 'D' ) );
}
/* We can't retrieve any stats prior to the new tracking being implemented */
try
{
$oldestLog = \IPS\Db::i()->select( 'MIN(time)', 'core_statistics', array( 'type=?', 'online_users' ) )->first();
if( !$minimumDate OR $oldestLog < $minimumDate->getTimestamp() )
{
$minimumDate = \IPS\DateTime::ts( $oldestLog );
}
}
catch( \UnderflowException $e )
{
/* We have nothing tracked, set minimum date to today */
$minimumDate = \IPS\DateTime::create();
}
$chart = new \IPS\Helpers\Chart\Callback(
\IPS\Http\Url::internal( 'app=core&module=stats&controller=onlineusers' ),
array( $this, 'getResults' ),
'',
array(
'isStacked' => TRUE,
'backgroundColor' => '#ffffff',
'colors' => array( '#10967e', '#ea7963' ),
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4
),
'AreaChart',
'none',
array( 'start' => \IPS\DateTime::ts( time() - ( 60 * 60 * 24 * 30 ) ), 'end' => \IPS\DateTime::create() ),
'',
$minimumDate
);
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack('members'), 'number' );
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack('guests'), 'number' );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('stats_onlineusers_title');
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart' );
$chart->showIntervals = FALSE;
$chart = \IPS\core\Statistics\Chart::loadFromExtension( 'core', 'OnlineUsers' )->getChart( \IPS\Http\Url::internal( "app=core&module=stats&controller=onlineusers" ) );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_stats_onlineusers');
\IPS\Output::i()->output = (string) $chart;
}
@@ -132,50 +83,4 @@ class _onlineusers extends \IPS\Dispatcher\Controller
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('prunesettings');
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('global')->block( 'prunesettings', $form, FALSE );
}
/**
* Fetch the results
*
* @param \IPS\Helpers\Chart\Callback $chart Chart object
* @return array
*/
public function getResults( $chart )
{
$where = array( array( 'type=?', 'online_users' ), array( "time>?", 0 ) );
if ( $chart->start )
{
$where[] = array( "time>?", $chart->start->getTimestamp() );
}
if ( $chart->end )
{
$where[] = array( "time<?", $chart->end->getTimestamp() );
}
$results = array();
foreach( \IPS\Db::i()->select( '*', 'core_statistics', $where, 'time ASC' ) as $row )
{
if( !isset( $results[ $row['time'] ] ) )
{
$results[ $row['time'] ] = array(
'time' => $row['time'],
\IPS\Member::loggedIn()->language()->get('members') => 0,
\IPS\Member::loggedIn()->language()->get('guests') => 0
);
}
if( $row['value_4'] == 'members' )
{
$results[ $row['time'] ][ \IPS\Member::loggedIn()->language()->get('members') ] = $row['value_1'];
}
if( $row['value_4'] == 'guests' )
{
$results[ $row['time'] ][ \IPS\Member::loggedIn()->language()->get('guests') ] = $row['value_1'];
}
}
return $results;
}
}
@@ -46,25 +46,7 @@ class _points extends \IPS\Dispatcher\Controller
*/
protected function manage()
{
$chart = new \IPS\Helpers\Chart\Database( \IPS\Http\Url::internal( 'app=core&module=stats&controller=points' ), 'core_points_log', 'datetime', '', array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'colors' => array( '#10967e', '#ea7963', '#de6470', '#6b9dde' ),
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4
), 'AreaChart', 'daily' );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('stats_points_title');
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart' );
$chart->enableHourly = FALSE;
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack('stats_member_points'), 'number', 'SUM(points)', TRUE );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('stats_points_title');
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart' );
$chart->showIntervals = TRUE;
$chart = \IPS\core\Statistics\Chart::loadFromExtension( 'core', 'Points' )->getChart( \IPS\Http\Url::internal( "app=core&module=stats&controller=points" ) );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_stats_points');
\IPS\Output::i()->output = (string) $chart;
}
@@ -45,66 +45,20 @@ class _preferences extends \IPS\Dispatcher\Controller
'theme' => 'stats_member_pref_theme',
'lang' => 'stats_member_pref_lang',
);
\IPS\Request::i()->tab ??= 'lang';
$activeTab = ( isset( \IPS\Request::i()->tab ) and array_key_exists( \IPS\Request::i()->tab, $tabs ) ) ? \IPS\Request::i()->tab : 'lang';
switch( $activeTab )
{
case 'theme':
$chart = new \IPS\Helpers\Chart;
$counts = iterator_to_array( \IPS\Db::i()->select( 'skin, COUNT(member_id) as count', 'core_members', array( "skin > ?", 0), NULL, NULL, "skin" )->setKeyField( 'skin' ) );
$chart->addHeader( "theme", "string" );
$chart->addHeader( \IPS\Member::loggedIn()->language()->get('chart_members'), "number" );
foreach( $counts as $id => $theme )
{
$chart->addRow( array( \IPS\Member::loggedIn()->language()->addToStack('core_theme_set_title_' . $id ), $theme['count'] ) );
}
$chart = \IPS\core\Statistics\Chart::loadFromExtension( 'core', 'Theme' )->getChart( \IPS\Http\Url::internal( "app=core&module=stats&controller=preferences&tab=theme" ) );
break;
case 'lang':
$chart = new \IPS\Helpers\Chart;
$counts = iterator_to_array( \IPS\Db::i()->select( 'language, COUNT(member_id) as count', 'core_members', array( "language > ?", 0), NULL, NULL, "language" )->setKeyField( 'language' ) );
$chart->addHeader( "language", "string" );
$chart->addHeader( \IPS\Member::loggedIn()->language()->get('chart_members'), "number" );
/* We need to make sure the language exists - otherwise apply the count to the default language. */
$rows = [];
foreach( $counts as $id => $lang )
{
try
{
$l = \IPS\Lang::load( $id );
if ( !isset( $rows[ $id ] ) )
{
$rows[ $id ] = array( 'title' => $l->title, 'count' => 0 );
}
$rows[ $id ]['count'] += $lang['count'];
}
catch( \OutOfRangeException $e )
{
if ( !isset( $rows[ \IPS\Lang::defaultLanguage() ] ) )
{
$rows[ \IPS\Lang::defaultLanguage() ] = array( 'title' => \IPS\Lang::load( \IPS\Lang::defaultLanguage() )->title, 'count' => 0 );
}
$rows[ \IPS\Lang::defaultLanguage() ]['count'] += $lang['count'];
}
}
/* Now add the rows to the chart */
foreach( $rows AS $row )
{
$chart->addRow( array( $row['title'], $row['count'] ) );
}
$chart = \IPS\core\Statistics\Chart::loadFromExtension( 'core', 'Language' )->getChart( \IPS\Http\Url::internal( "app=core&module=stats&controller=preferences&tab=lang" ) );
break;
}
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('stats_' . $activeTab . '_title');
$chart = $chart->render('PieChart', array(
'backgroundColor' => '#ffffff',
'pieHole' => 0.4,
@@ -46,26 +46,7 @@ class _rankprogression extends \IPS\Dispatcher\Controller
*/
protected function manage()
{
$chart = new \IPS\Helpers\Chart;
$chart->addHeader( "Rank", 'string' );
$chart->addHeader( "Days", 'number' );
$data = \IPS\Db::i()->select( 'new_rank, AVG(time_to_new_rank) as time_to_rank', 'core_points_log', array('time_to_new_rank IS NOT NULL'), 'core_member_ranks.points ASC', NULL, ['new_rank'] );
$data->join( 'core_member_ranks', 'core_member_ranks.id = core_points_log.new_rank' );
foreach ( $data as $row )
{
try
{
$rank = \IPS\core\Achievements\Rank::load( $row['new_rank'] );
}
catch ( \Exception $e )
{
continue;
}
$chart->addRow( array( $rank->_title, floor( $row['time_to_rank'] / 86400 ) ) );
}
$chart = \IPS\core\Statistics\Chart::loadFromExtension( 'core', 'RankProgression' )->getChart( \IPS\Http\Url::internal( "app=core&module=stats&controller=rankprogression" ) );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_stats_rankprogression');
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'stats' )->rankprogressionmessage();
\IPS\Output::i()->output .= $chart->render( 'ScatterChart', array(
@@ -41,23 +41,8 @@ class _registrationstats extends \IPS\Dispatcher\Controller
{
/* Check permission */
\IPS\Dispatcher::i()->checkAcpPermission( 'registrations_manage', 'core', 'stats' );
$chart = new \IPS\Helpers\Chart\Database( \IPS\Http\Url::internal( 'app=core&module=stats&controller=registrationstats' ), 'core_members', 'joined', '', array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'colors' => array( '#10967e' ),
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4
) );
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack('stats_new_registrations'), 'number', 'COUNT(*)', FALSE );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('stats_registrations_title');
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart' );
/* fetch only successful registered members ; if this needs to be changed, please review the other areas where we have the name<>? AND email<>? condition */
$chart->where[] = array( 'completed=?', true );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_stats_registrationstats');
\IPS\Output::i()->output = (string) $chart;
\IPS\Output::i()->output = (string) \IPS\core\Statistics\Chart::loadFromExtension( 'core', 'Registrations' )->getChart( \IPS\Http\Url::internal( 'app=core&module=stats&controller=registrationstats' ) );
}
}
+1 -25
View File
@@ -47,31 +47,7 @@ class _spam extends \IPS\Dispatcher\Controller
*/
protected function manage()
{
$chart = new \IPS\Helpers\Chart\Database( \IPS\Http\Url::internal( 'app=core&module=stats&controller=spam' ), 'core_spam_service_log', 'log_date', '', array(
'isStacked' => TRUE,
'backgroundColor' => '#ffffff',
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4
), 'LineChart', 'monthly', array( 'start' => 0, 'end' => 0 ), array( 'log_code', 'log_date' ), 'spam' );
$chart->groupBy = 'log_code';
foreach( array( 1,2,3,4 ) as $v )
{
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack('spam_service_action_stats_' . $v ), 'number', 'COUNT(*)', TRUE, $v );
}
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('stats_spam_title');
$chart->availableTypes = array( 'LineChart', 'AreaChart', 'ColumnChart', 'BarChart' );
$chart->tableParsers = array(
'log_date' => function( $val )
{
return (string) \IPS\DateTime::ts( $val );
}
);
$chart = \IPS\core\Statistics\Chart::loadFromExtension( 'core', 'Spam' )->getChart( \IPS\Http\Url::internal( "app=core&module=stats&controller=spam" ) );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_stats_spam');
\IPS\Output::i()->output = (string) $chart;
@@ -51,52 +51,19 @@ class _warnings extends \IPS\Dispatcher\Controller
'reason' => 'stats_warnings_reason',
'suspended' => 'stats_warnings_suspended',
);
$activeTab = ( isset( \IPS\Request::i()->tab ) and array_key_exists( \IPS\Request::i()->tab, $tabs ) ) ? \IPS\Request::i()->tab : 'reason';
/* Make sure tab is set, otherwise saved charts may not show up when loading the page. */
\IPS\Request::i()->tab ??= 'reason';
$activeTab = ( array_key_exists( \IPS\Request::i()->tab, $tabs ) ) ? \IPS\Request::i()->tab : 'reason';
if ( $activeTab === 'reason' )
{
$chart = new \IPS\Helpers\Chart\Database( \IPS\Http\Url::internal( 'app=core&module=stats&controller=warnings&tab=' . $activeTab ), 'core_members_warn_logs', 'wl_date', '', array(
'isStacked' => TRUE,
'backgroundColor' => '#ffffff',
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4
), 'LineChart', 'monthly', array( 'start' => 0, 'end' => 0 ), array( 'wl_reason', 'wl_date' ), 'warnings' );
$chart->groupBy = 'wl_reason';
foreach( \IPS\core\Warnings\Reason::roots() as $reason )
{
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack('core_warn_reason_' . $reason->id ), 'number', 'COUNT(*)', TRUE, $reason->id );
}
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('stats_warnings_title');
$chart->availableTypes = array( 'LineChart', 'AreaChart', 'ColumnChart', 'BarChart' );
$chart = \IPS\core\Statistics\Chart::loadFromExtension( 'core', 'WarningReasons' )->getChart( \IPS\Http\Url::internal( 'app=core&module=stats&controller=warnings&tab=reason' ) );
}
else if ( $activeTab === 'suspended' )
{
$chart = new \IPS\Helpers\Chart\Database( \IPS\Http\Url::internal( 'app=core&module=stats&controller=warnings&tab=' . $activeTab ), 'core_members_warn_logs', 'wl_date', '', array(
'isStacked' => TRUE,
'backgroundColor' => '#ffffff',
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4
), 'LineChart', 'monthly', array( 'start' => 0, 'end' => 0 ), array( 'wl_reason', 'wl_date' ), 'warnings' );
$chart->where = array( 'wl_suspend IS NOT NULL' );
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack('suspended' ), 'number', 'COUNT(*)', TRUE );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('stats_warnings_title');
$chart->availableTypes = array( 'LineChart', 'AreaChart', 'ColumnChart', 'BarChart' );
$chart = \IPS\core\Statistics\Chart::loadFromExtension( 'core', 'WarningSuspended' )->getChart( \IPS\Http\Url::internal( 'app=core&module=stats&controller=warnings&tab=suspended' ) );
}
$chart->tableParsers = array(
'wl_date' => function( $val )
{
return (string) \IPS\DateTime::ts( $val );
}
);
if ( \IPS\Request::i()->isAjax() )
{
@@ -107,7 +107,6 @@ class _support extends \IPS\Dispatcher\Controller
}
catch( \Exception $e )
{
\IPS\Log::log('Truncate table core_error_logs filed');
}
}
@@ -119,7 +118,6 @@ class _support extends \IPS\Dispatcher\Controller
}
catch( \Exception $e )
{
\IPS\Log::log('Truncate table core_mail_error_logs filed');
}
}
@@ -865,7 +863,7 @@ class _support extends \IPS\Dispatcher\Controller
->request()
->get();
$headerKeys = array_map( 'mb_strtolower', array_keys( $request->httpHeaders ) );
$headerKeys = \is_array( $request->httpHeaders ) ? array_map( 'mb_strtolower', array_keys( $request->httpHeaders ) ) : [];
if( \in_array( 'cf-cache-status', $headerKeys ) )
{
@@ -1649,6 +1647,8 @@ class _support extends \IPS\Dispatcher\Controller
$chart->addSeries( \IPS\Member::loggedIn()->language()->get('health_error_log_title'), 'number', FALSE );
$chart->addSeries( \IPS\Member::loggedIn()->language()->get('health_email_error_log_title'), 'number', FALSE );
$chart->title = NULL;
$chart->showFilterTabs = FALSE;
$chart->showSave = FALSE;
$chart->availableTypes = array( 'LineChart' );
if( \IPS\Request::i()->isAjax() )