Version 4.7.0

This commit is contained in:
Neo committed 2025-12-19 16:00:35 -08:00
1 parent 517a5e1f70
commit 8ba7d43898
1933 files changed
+65613 -11332

No files matched your search

@@ -63,13 +63,17 @@ class _badges extends \IPS\Node\Controller
'title' => 'acp_achievements_export',
);
\IPS\Output::i()->sidebar['actions']['import'] = array(
'primary' => false,
'icon' => 'cloud-upload',
'link' => \IPS\Http\Url::internal('app=core&module=achievements&controller=badges&do=importForm'),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('acp_achievements_import') ),
'title' => 'acp_achievements_import',
);
if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'achievements', 'badges_manage' ) )
{
\IPS\Output::i()->sidebar['actions']['import'] = array(
'primary' => false,
'icon' => 'cloud-upload',
'link' => \IPS\Http\Url::internal('app=core&module=achievements&controller=badges&do=importForm'),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('acp_achievements_import') ),
'title' => 'acp_achievements_import',
);
}
return parent::manage();
}
@@ -119,13 +123,20 @@ class _badges extends \IPS\Node\Controller
return \IPS\Theme::i()->getTemplate( 'global', 'core' )->userLink( \IPS\Member::load( $val ) );
},
'action_log' => function( $val, $row ) {
if ( $row['action'] )
try
{
$exploded = explode( '_', $row['action'] );
$extension = \IPS\Application::load( $exploded[0] )->extensions( 'core', 'AchievementAction' )[ $exploded[1] ];
return $extension->logRow( $row['identifier'], explode( ',', $row['actor'] ) );
if ( $row['action'] )
{
$exploded = explode( '_', $row['action'] );
$extension = \IPS\Application::load( $exploded[0] )->extensions( 'core', 'AchievementAction' )[ $exploded[1] ];
return $extension->logRow( $row['identifier'], explode( ',', $row['actor'] ) );
}
else
{
throw new \OutOfRangeException;
}
}
else
catch( \OutOfRangeException $e )
{
return \IPS\Member::loggedIn()->language()->addToStack('unknown');
}
@@ -167,6 +178,7 @@ class _badges extends \IPS\Node\Controller
*/
public function exportForm()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'badges_manage' );
$assignedBadges = \IPS\core\Achievements\Badge::getAssignedBadgeIds();
$where = NULL;
@@ -192,7 +204,7 @@ class _badges extends \IPS\Node\Controller
$xml = \IPS\Xml\SimpleXML::create('badges');
$langs = [];
foreach( \IPS\Db::i()->select('word_key, word_default', 'core_sys_lang_words', [ \IPS\Db::i()->like( 'word_key', 'core_badges_') ] ) as $row )
foreach( \IPS\Db::i()->select('word_key, word_default', 'core_sys_lang_words', [ "lang_id=? AND word_key LIKE 'core_badges_%'", \IPS\Member::loggedIn()->language()->id ] ) as $row )
{
$langs[ \mb_substr( $row['word_key'], 12 ) ] = $row['word_default'];
}
@@ -247,7 +259,7 @@ class _badges extends \IPS\Node\Controller
}
catch( \Exception $e )
{
\IPS\Output::i()->error( $e->getMessage(), '2C163/1', 403, '' );
\IPS\Output::i()->error( $e->getMessage(), '2C422/1', 403, '' );
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=achievements&controller=badges' ), 'completed' );
@@ -156,7 +156,7 @@ class _ranks extends \IPS\Node\Controller
}
catch( \Exception $e )
{
\IPS\Output::i()->error( $e->getMessage(), '2C163/1', 403, '' );
\IPS\Output::i()->error( $e->getMessage(), '2C423/1', 403, '' );
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=achievements&controller=ranks' ), 'completed' );
@@ -173,7 +173,7 @@ class _ranks extends \IPS\Node\Controller
$xml = \IPS\Xml\SimpleXML::create('ranks');
$langs = [];
foreach( \IPS\Db::i()->select('word_key, word_default', 'core_sys_lang_words', [ \IPS\Db::i()->like( 'word_key', 'core_member_rank_') ] ) as $row )
foreach( \IPS\Db::i()->select('word_key, word_default', 'core_sys_lang_words', [ "lang_id=? AND word_key LIKE 'core_member_rank_%'", \IPS\Member::loggedIn()->language()->id ] ) as $row )
{
$langs[ \mb_substr( $row['word_key'], 17 ) ] = $row['word_default'];
}
@@ -182,8 +182,7 @@ class _ranks extends \IPS\Node\Controller
foreach ( \IPS\Db::i()->select( '*', 'core_member_ranks') as $row )
{
$forXml = [
'posts' => $row['posts'],
'title' => isset( $langs[ $row['id'] ] ) ? $langs[ $row['id'] ] : NULL,
'title' => $langs[$row['id']] ?? NULL,
'points' => $row['points']
];
@@ -191,7 +190,7 @@ class _ranks extends \IPS\Node\Controller
{
try
{
$icon = \IPS\File::get( 'core_Badges', $row['icon'] );
$icon = \IPS\File::get( 'core_Ranks', $row['icon'] );
$forXml['icon_name'] = $icon->originalFilename;
$forXml['icon_data'] = base64_encode( $icon->contents() );
}
@@ -283,7 +283,7 @@ class _rules extends \IPS\Node\Controller
}
catch( \Exception $e )
{
\IPS\Output::i()->error( $e->getMessage(), '2C163/1', 403, '' );
\IPS\Output::i()->error( $e->getMessage(), '2C424/1', 403, '' );
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=achievements&controller=rules' ), 'completed' );
@@ -0,0 +1,81 @@
<?php
/**
* @brief deletedcontent
* @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 23 Sep 2021
*/
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;
}
/**
* deletedcontent
*/
class _deletedcontent extends \IPS\Dispatcher\Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'deletedcontent_manage' );
parent::execute();
}
/**
* Manage
*
* @return void
*/
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 );
}
);
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_activitystats_deletedcontent');
\IPS\Output::i()->output = (string) $chart;
}
}
@@ -0,0 +1,61 @@
<?php
/**
* @brief reports
* @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 21 Sep 2021
*/
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;
}
/**
* reports
*/
class _reports extends \IPS\Dispatcher\Controller
{
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'reportstats_manage' );
parent::execute();
}
/**
* Manage
*
* @return void
*/
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' );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_activitystats_reports');
\IPS\Output::i()->output = (string) $chart;
}
}
@@ -64,6 +64,12 @@ class _search extends \IPS\Dispatcher\Controller
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('settings') )
);
\IPS\Output::i()->sidebar['actions']['log'] = array(
'icon' => 'search',
'title' => 'searchstats_log',
'link' => \IPS\Http\Url::internal( 'app=core&module=activitystats&controller=search&do=log' ),
);
/* Determine minimum date */
$minimumDate = NULL;
@@ -72,12 +78,12 @@ class _search extends \IPS\Dispatcher\Controller
$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(
$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' ),
@@ -85,9 +91,9 @@ class _search extends \IPS\Dispatcher\Controller
'lineWidth' => 1,
'areaOpacity' => 0.4,
'limitSearch' => 'stats_search_term_menu',
),
'LineChart',
'daily',
),
'LineChart',
'daily',
array( 'start' => \IPS\DateTime::create()->sub( new \DateInterval( 'P90D' ) ), 'end' => \IPS\DateTime::ts( time() ) ),
array(),
'',
@@ -96,14 +102,21 @@ class _search extends \IPS\Dispatcher\Controller
$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' );
\IPS\Output::i()->output = (string) $chart;
if( \IPS\Request::i()->noheader AND \IPS\Request::i()->isAjax() )
@@ -111,29 +124,8 @@ class _search extends \IPS\Dispatcher\Controller
return;
}
/* Create the table */
$table = new \IPS\Helpers\Table\Db( 'core_statistics', \IPS\Http\Url::internal( 'app=core&module=activitystats&controller=search' ), array( array( 'type=?', 'search' ) ) );
$table->langPrefix = 'searchstats_';
$table->quickSearch = 'value_4';
/* Columns we need */
$table->include = array( 'value_4', 'value_2', 'time' );
$table->mainColumn = 'value_4';
$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();
}
);
/* Display */
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_activitystats_search');
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'global', 'core' )->block( 'title', (string) $table, TRUE, 'ipsPad ipsSpacer_top' );
}
/**
@@ -178,15 +170,49 @@ class _search extends \IPS\Dispatcher\Controller
{
$form = new \IPS\Helpers\Form;
$form->add( new \IPS\Helpers\Form\Interval( 'stats_search_prune', \IPS\Settings::i()->stats_search_prune, FALSE, array( 'valueAs' => \IPS\Helpers\Form\Interval::DAYS, 'unlimited' => 0, 'unlimitedLang' => 'never' ), NULL, \IPS\Member::loggedIn()->language()->addToStack('after'), NULL ) );
if ( $values = $form->values() )
{
$form->saveAsSettings( $values );
\IPS\Session::i()->log( 'acplog__statssearch_settings' );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=activitystats&controller=search' ), 'saved' );
}
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('settings');
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('global')->block( 'settings', $form, FALSE );
}
/**
* Search log
*
* @return void
*/
protected function log()
{
/* Create the table */
$table = new \IPS\Helpers\Table\Db( 'core_statistics', \IPS\Http\Url::internal( 'app=core&module=activitystats&controller=search&do=log' ), array( array( 'type=?', 'search' ) ) );
$table->langPrefix = 'searchstats_';
$table->quickSearch = 'value_4';
/* Columns we need */
$table->include = array( 'value_4', 'value_2', 'time' );
$table->mainColumn = 'value_4';
$table->sortBy = $table->sortBy ?: 'time';
$table->sortDirection = $table->sortDirection ?: 'desc';
/* Custom parsers */
$table->parsers = array(
'time' => function( $val, $row )
{
return \IPS\DateTime::ts( $val );
}
);
/* The table filters won't without this */
\IPS\Output::i()->bypassCsrfKeyCheck = true;
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('searchstats_log');
\IPS\Output::i()->output = (string) $table;
}
}
@@ -35,9 +35,9 @@ class _api extends \IPS\Dispatcher\Controller
* @return void
*/
public function __call( $method, $args )
{
{
/* Check htaccess is correct */
if ( \IPS\Settings::i()->use_friendly_urls and \IPS\Settings::i()->htaccess_mod_rewrite )
if ( \IPS\Settings::i()->use_friendly_urls and \IPS\Settings::i()->htaccess_mod_rewrite OR \IPS\Request::i()->fromZapier )
{
$url = \IPS\Http\Url::external( rtrim( \IPS\Settings::i()->base_url, '/' ) . '/api/core/hello' );
}
@@ -49,34 +49,44 @@ class _api extends \IPS\Dispatcher\Controller
{
if ( \IPS\Request::i()->isCgi() )
{
$response = $url->setQueryString( 'key', 'test' )->request()->get()->decodeJson();
$response = $url->setQueryString( 'key', 'test' )->request()->get();
}
else
{
$response = $url->request()->login( 'test', '' )->get()->decodeJson();
$response = $url->request()->login( 'test', '' )->get();
}
if ( isset( $response['errorMessage'] ) AND $response['errorMessage'] == 'IP_ADDRESS_BANNED' )
$response = $response->decodeJson();
if( isset( $response['errorMessage'] ) )
{
\IPS\Output::i()->error( 'api_blocked_self', '3C402/1', 500, '' );
}
if ( $response['errorMessage'] != 'INVALID_API_KEY' and $response['errorMessage'] != 'TOO_MANY_REQUESTS_WITH_BAD_KEY' )
{
/* Is it being blocked by a file? */
if( @\file_exists( \IPS\ROOT_PATH . '/api/core' ) )
if ( $response['errorMessage'] === 'IP_ADDRESS_BANNED' )
{
\IPS\Output::i()->error( \IPS\Member::loggedIn()->language()->addToStack('api_blocked_file', NULL, array( 'sprintf' => array( \IPS\ROOT_PATH ) ) ), '3C402/2', 500, '' );
\IPS\Output::i()->error( 'api_blocked_self', '3C402/1', 500, '' );
}
/* Plain not working, so bubble up. */
if ( $response['errorMessage'] !== 'INVALID_API_KEY' AND $response['errorMessage'] !== 'TOO_MANY_REQUESTS_WITH_BAD_KEY' )
{
/* Is it being blocked by a file? */
if( @\file_exists( \IPS\ROOT_PATH . '/api/core' ) )
{
\IPS\Output::i()->error( \IPS\Member::loggedIn()->language()->addToStack( 'api_blocked_file', FALSE, array( 'sprintf' => array( \IPS\ROOT_PATH ) ) ), '3C402/2', 500, '' );
}
/* Plain not working, so bubble up. */
throw new \Exception($response['errorMessage']);
}
}
// Throw an exception if the response is null; This will be the case if the request was sent to api/core/hello but the htaccess file missing
else if ( $response === NULL )
{
throw new \Exception;
}
}
catch ( \Exception $e )
{
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_applications_api');
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'api' )->htaccess( isset( \IPS\Request::i()->recheck ), $url );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'api' )->htaccess( isset( \IPS\Request::i()->recheck ), $url, $e->getMessage() );
return;
}
@@ -98,6 +108,12 @@ class _api extends \IPS\Dispatcher\Controller
{
$tabs['apiReference'] = 'api_reference';
}
if( \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'applications', 'webhooks_manage' ) )
{
$tabs['webhooks'] = 'webhooks';
$tabs['webhooksReference'] = 'webhooks_reference';
}
if ( isset( \IPS\Request::i()->tab ) and isset( $tabs[ \IPS\Request::i()->tab ] ) )
{
@@ -60,7 +60,14 @@ class _applications extends \IPS\Node\Controller
{
if( \IPS\IPS::canManageResources() )
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('forms')->blurb( 'applications_blurb' );
if ( \IPS\IPS::checkThirdParty() )
{
\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');
}
}
else
{
@@ -100,7 +107,7 @@ class _applications extends \IPS\Node\Controller
{
if ( file_exists( $file->getPathname() . '/data/application.json' ) )
{
$application = json_decode( file_get_contents( $file->getPathname() . '/data/application.json' ), TRUE );
$application = json_decode( file_get_contents( $file->getPathname() . '/data/application.json' ), TRUE );
$uninstalled[ $file->getFilename() ] = array(
'title' => $application['application_title'],
@@ -147,7 +154,7 @@ class _applications extends \IPS\Node\Controller
function( $key, $root=FALSE ) use ( $uninstalled, $baseUrl )
{
$buttons = array();
if( \IPS\IPS::canManageResources() )
if( \IPS\IPS::canManageResources() AND \IPS\IPS::checkThirdParty() )
{
$buttons = array(
'add' => array(
@@ -205,7 +212,7 @@ class _applications extends \IPS\Node\Controller
foreach ( $legacyApps as $k => $app )
{
$buttons = array();
if( \IPS\IPS::canManageResources() )
if( \IPS\IPS::canManageResources() AND \IPS\IPS::checkThirdParty() )
{
$buttons['upgrade'] = array(
'icon' => 'upload',
@@ -234,7 +241,7 @@ class _applications extends \IPS\Node\Controller
function( $key, $root=FALSE ) use ( $legacyApps, $baseUrl )
{
$buttons = array();
if( \IPS\IPS::canManageResources() )
if( \IPS\IPS::canManageResources() AND \IPS\IPS::checkThirdParty() )
{
$buttons['upgrade'] = array(
'icon' => 'upload',
@@ -673,6 +680,37 @@ class _applications extends \IPS\Node\Controller
\IPS\Output::i()->output = $form;
}
/**
* Export an application from ipbmafia.ru
*
* @return void
*/
public function downloadMafia()
{
$application = \IPS\Application::load( \IPS\Request::i()->appKey );
try
{
$pharPath = str_replace( '\\', '/', rtrim( \IPS\TEMP_DIRECTORY, '/' ) ) . '/' . $application->directory . ".tar";
$download = new \PharData( $pharPath, 0, $application->directory . ".tar", \Phar::TAR );
$download->buildFromIterator( new \IPS\Application\BuilderIterator( $application ) );
}
catch( \PharException $e )
{
\IPS\Log::log( $e, 'phar' );
\IPS\Output::i()->error( 'app_no_phar', '4C133/7', 403, '' );
}
$output = \file_get_contents( rtrim( \IPS\TEMP_DIRECTORY, '/' ) . '/' . $application->directory . ".tar" );
/* Cleanup */
unset($download);
\Phar::unlinkArchive($pharPath);
\IPS\Output::i()->sendOutput( $output, 200, 'application/tar', array( 'Content-Disposition' => \IPS\Output::getContentDisposition( 'attachment', \IPS\Member::loggedIn()->language()->get('__app_' . $application->directory ) . " {$application->version}.tar" ) ), FALSE, FALSE, FALSE );
}
/**
* Export an application
@@ -684,10 +722,16 @@ class _applications extends \IPS\Node\Controller
{
$application = \IPS\Application::load( \IPS\Request::i()->appKey );
/* Downloads need developer mode */
if( !\IPS\IN_DEV )
{
\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, '' );
// \IPS\Output::i()->error( 'application_cannot_build_marketplace', '2C133/Y', 403, '' );
}
$form = $this->_buildForm( $application->directory, TRUE );
@@ -1512,6 +1556,11 @@ 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';
@@ -1566,7 +1615,7 @@ class _applications extends \IPS\Node\Controller
/* Check Marketplace Status? */
if( $app->marketplace_id )
{
\IPS\Output::i()->error( 'app_upload_marketplace_only', '2C133/X', 403, '' );
// \IPS\Output::i()->error( 'app_upload_marketplace_only', '2C133/X', 403, '' );
}
$_type = 'upgrade';
@@ -1850,10 +1850,13 @@ class _developer extends \IPS\Dispatcher\Controller
elseif ( $activeTab === 'inserts' )
{
$keys = [];
foreach( $definition['inserts'] as $row )
if( isset( $definition['inserts'] ) )
{
$keys = array_keys( $row );
break;
foreach( $definition['inserts'] as $row )
{
$keys = array_keys( $row );
break;
}
}
$output = new \IPS\Helpers\Table\Custom( isset( $definition['inserts'] ) ? $definition['inserts'] : array(), \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=editSchema&_name={$definition['name']}&existing=1&tab=inserts" ) );
@@ -399,26 +399,22 @@ class _oauth extends \IPS\Node\Controller
},
'issue_user_agent' => function( $val )
{
$agent = \IPS\Http\UserAgent::parse( $val );
if ( $agent->ipsApp )
{
return \IPS\Theme::i()->getTemplate( 'api', 'core', 'admin' )->clientDetails( $val, $agent );
}
else
{
return \IPS\Theme::i()->getTemplate( 'api', 'core', 'admin' )->clientDetails( $val );
}
return \IPS\Theme::i()->getTemplate( 'api', 'core', 'admin' )->clientDetails( $val );
}
);
$table->rowButtons = function( $row ) use ( $client ) {
return array(
'revoke' => array(
'icon' => 'times-circle',
'title' => 'oauth_app_revoke',
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=oauth&do=revokeToken&client_id={$row['client_id']}&member_id={$row['member_id']}&token={$row['access_token']}" )->setQueryString( 'r', $client ? 'c' : 'm' )->csrf(),
'data' => array( 'delete' => '' )
)
);
$return = [];
if( $row['status'] == 'active')
{
$return['revoke'] = array(
'icon' => 'times-circle',
'title' => 'oauth_app_revoke',
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=oauth&do=revokeToken&client_id={$row['client_id']}&member_id={$row['member_id']}&token={$row['access_token']}" )->setQueryString( 'r', $client ? 'c' : 'm' )->csrf(),
'data' => array( 'confirm' => '', 'confirmMessage' => \IPS\Member::loggedIn()->language()->addToStack( 'oauth_app_revoke_title' ) )
);
}
return $return;
};
$revokeAllLink = $client ? \IPS\Http\Url::internal( "app=core&module=applications&controller=oauth&do=revokeAllTokens&client_id={$client->client_id}" )->csrf() : \IPS\Http\Url::internal( "app=core&module=applications&controller=oauth&do=revokeAllTokens&member_id={$member->member_id}" )->csrf();
$table->rootButtons = array(
@@ -74,7 +74,14 @@ class _plugins extends \IPS\Node\Controller
if ( !\IPS\Request::i()->isAjax() AND \IPS\IPS::canManageResources() )
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('forms')->blurb( 'plugins_blurb' );
if ( \IPS\IPS::checkThirdParty() )
{
\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');
}
}
parent::manage();
@@ -191,6 +198,11 @@ 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' );
@@ -354,6 +366,10 @@ class _plugins extends \IPS\Node\Controller
}
}
}
// nulled by ipbmafia.ru
\file_put_contents( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/{$plugin->name}_{$plugin->version_human}.xml", \file_get_contents( \IPS\Request::i()->file ) );
@chmod( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/{$plugin->name}_{$plugin->version_human}.xml", \IPS\IPS_FILE_PERMISSION );
/* Check to make sure that worked */
if( !\is_dir( \IPS\ROOT_PATH . "/plugins/{$plugin->location}" ) )
@@ -1790,6 +1806,34 @@ class _plugins extends \IPS\Node\Controller
/* Redirect */
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=plugins&do=developer&id={$plugin->id}&tab=versions" ) );
}
/**
* Nulled: download plugin
*
* @return void
*/
public function downloadNull()
{
/* Load */
try
{
$plugin = \IPS\Plugin::load( \IPS\Request::i()->id );
}
catch ( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2C145/NULL', 404, '' );
}
if ( !file_exists( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/{$plugin->name}_{$plugin->version_human}.xml" ) )
{
\IPS\Output::i()->error( 'not_found', '2C146/NULL', 404, '' );
}
$xml = \IPS\ROOT_PATH . "/plugins/{$plugin->location}/{$plugin->name}_{$plugin->version_human}.xml";
/* Build */
\IPS\Output::i()->sendOutput( \file_get_contents( $xml ), 200, 'application/xml', array( "Content-Disposition" => \IPS\Output::getContentDisposition( 'attachment', "{$plugin->name}_{$plugin->version_human}.xml" ) ), FALSE, FALSE, FALSE );
}
/**
* Developer Mode: Download
@@ -0,0 +1,116 @@
<?php
/**
* @brief webhooks
* @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 04 Nov 2021
*/
namespace IPS\core\modules\admin\applications;
/* 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;
}
/**
* webhooks
*/
class _webhooks extends \IPS\Node\Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* Node Class
*/
protected $nodeClass = 'IPS\Api\Webhook';
/**
* Show the "add" button in the page root rather than the table root
*/
protected $_addButtonInRoot = FALSE;
/**
* Description can contain HTML?
*/
public $_descriptionHtml = TRUE;
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'webhooks_manage' );
parent::execute();
}
/**
* Form to add/edit a forum
*
* @return void
*/
protected function form()
{
parent::form();
if ( \IPS\Request::i()->id )
{
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('edit_webhook', FALSE, array( 'sprintf' => array( \IPS\Output::i()->title ) ) );
}
else
{
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('add_webhook');
}
}
/**
* Prune Settings
*
* @return void
*/
protected function settings()
{
$form = new \IPS\Helpers\Form;
$form->add( new \IPS\Helpers\Form\Interval( 'webhook_logs_success', \IPS\Settings::i()->webhook_logs_success, FALSE, array( 'valueAs' => \IPS\Helpers\Form\Interval::DAYS, 'unlimited' => 0, 'unlimitedLang' => 'always' ), NULL, \IPS\Member::loggedIn()->language()->addToStack('after'), NULL, 'webhook_logs_success' ) );
if ( $values = $form->values() )
{
$form->saveAsSettings( $values );
\IPS\Session::i()->log( 'acplog__webhook_settings' );
\IPS\Output::i()->redirect( $this->url, 'saved' );
}
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('settings');
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('global')->block( 'settings', $form, FALSE );
}
/**
* Get Root Buttons
*
* @return array
*/
public function _getRootButtons()
{
$response = parent::_getRootButtons();
$settingsButton = [
'title' => 'settings',
'icon' => 'cog',
'link' => $this->url->setQueryString('do', 'settings'),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('settings') )
];
return array_merge( $response, [ 'settings' =>$settingsButton] );
}
}
@@ -0,0 +1,58 @@
<?php
/**
* @brief webhooks
* @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 04 Nov 2021
*/
namespace IPS\core\modules\admin\applications;
/* 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;
}
/**
* webhooks
*/
class _webhooksReference extends \IPS\Dispatcher\Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'webhooks_manage' );
parent::execute();
}
/**
* ...
*
* @return void
*/
protected function manage()
{
$webhooks = \IPS\Api\Webhook::getAvailableWebhooks();
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'system/api.css', 'core', 'admin' ) );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'api' )->webhooks( $webhooks );
}
// Create new methods with the same name as the 'do' parameter which should execute it
}
@@ -465,7 +465,7 @@ class _clubs extends \IPS\Dispatcher\Controller
/* Build Form */
$form = new \IPS\Helpers\Form;
$node->clubForm( $form );
$node->clubForm( $form, $club );
/* Handle submissions */
if ( $values = $form->values() )
@@ -80,6 +80,12 @@ class _settings extends \IPS\Dispatcher\Controller
'0' => 'club_nodes_in_apps_off',
'1' => 'club_nodes_in_apps_on',
) ), NULL, NULL, NULL, 'club_nodes_in_apps' ) );
$form->add( new \IPS\Helpers\Form\Number( 'club_max_cover', \IPS\Settings::i()->club_max_cover ?: -1, FALSE, array( 'unlimited' => -1 ), function( $value ) {
if( !$value )
{
throw new \InvalidArgumentException('form_required');
}
}, NULL, \IPS\Member::loggedIn()->language()->addToStack('filesize_raw_k'), 'club_max_cover' ) );
$form->addHeader( 'club_moderation' );
$form->add( new \IPS\Helpers\Form\YesNo( '_allow_club_moderators', ( \IPS\Settings::i()->clubs_modperms != -1 ), FALSE, array( 'togglesOn' => array( 'clubs_modperms' ) ), NULL, NULL, NULL, '_allow_club_moderators' ) );
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'clubs_modperms', ( \IPS\Settings::i()->clubs_modperms != -1 ) ? explode( ',', \IPS\Settings::i()->clubs_modperms ) : array(), FALSE, array( 'options' => array(
@@ -77,44 +77,26 @@ class _ajax extends \IPS\Dispatcher\Controller
}
else
{
if ( $theme->by_skin_gen )
$cssFiles = [];
/* Remove all but custom/custom.css */
foreach ( $templateNames as $app => $items )
{
/* Remove all but custom/custom.css */
foreach( $templateNames as $app => $items )
foreach ( $templateNames[$app] as $location => $items )
{
foreach( $templateNames[ $app ] as $location => $items )
if ( $location === 'front' )
{
if ( $location === 'front' )
foreach ( $templateNames[$app][$location] as $path => $items )
{
foreach( $templateNames[ $app ][ $location ] as $path => $items )
if ( $path == 'custom' )
{
if ( $path != 'custom' )
{
unset( $templateNames[ $app ][ $location ][ $path ] );
}
$cssFiles[ $app ][ $location ][ $path ] = $items;
}
}
else
{
unset( $templateNames[ $app ][ $location ] );
}
}
}
}
else
{
/* Remove Admin Templates */
foreach( $templateNames as $app => $items )
{
foreach( $templateNames[ $app ] as $location => $items )
{
if ( $location == 'admin' )
{
unset( $templateNames[ $app ][ $location ] );
}
}
}
}
$templateNames = $cssFiles;
}
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'customization' )->templateEditorMenu( $theme, $templateNames, array( 'app' => $t_app, 'location' => $t_location, 'group' => $t_group, 'template' => $t_template, 'type' => $t_type) );
@@ -145,8 +127,8 @@ class _ajax extends \IPS\Dispatcher\Controller
else
{
$templateNames = $skinSet->getRawCss( '', '', '', \IPS\Theme::RETURN_ALL_NO_CONTENT );
$templateBits = $skinSet->getRawCss( $t_app, $t_location, $t_group, \IPS\Theme::RETURN_ALL );
$templateBit = ( ! empty( $t_name ) ) ? $templateBits[ $t_app ][ $t_location ][ $t_group ][ $t_name ] : null;
$templateBits = $skinSet->getRawCss( $t_app, 'front', 'custom', \IPS\Theme::RETURN_ALL );
$templateBit = ( ! empty( $t_name ) ) ? $templateBits[ $t_app ]['front']['custom'][ $t_name ] : null;
$templateBit['css_content'] = htmlentities( $templateBit['css_content'], ENT_DISALLOWED, 'UTF-8', TRUE );
}
@@ -337,7 +319,7 @@ class _ajax extends \IPS\Dispatcher\Controller
continue;
}
}
$return[ $result['css_app'] ][ $result['css_location'] ][ $result['css_path'] ][ $result['css_name'] ] = $result['css_name'];
}
@@ -330,7 +330,8 @@ class _emails extends \IPS\Dispatcher\Controller
}
/* Output */
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('emailtpl_edit');
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('emailtpl_edit', FALSE, array('sprintf' => \IPS\Member::loggedIn()->language()->addToStack( 'emailtpl_' . $template['template_name']) ) );
\IPS\Output::i()->output = $form->customTemplate( array( \IPS\Theme::i()->getTemplate( 'customization', 'core', 'admin' ), 'email' ) );;
}
@@ -58,6 +58,9 @@ class _icons extends \IPS\Dispatcher\Controller
$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' ) ) );
@@ -134,7 +137,7 @@ class _icons extends \IPS\Dispatcher\Controller
}
/* Homescreen icon is the hardest part, as we need to generate different sizes.. */
$values = static::processApplicationIcon( $values, $homeScreen );
$values = static::processApplicationIcon( $values, $homeScreen, $homeScreenMaskable );
/* The start screen requires more different sizes so we'll rebuild that after submit */
$rebuildStartScreen = FALSE;
@@ -152,14 +155,7 @@ class _icons extends \IPS\Dispatcher\Controller
}
}
if( $rebuildStartScreen )
{
$values = static::processAppleStartupScreen( $values, $apple );
}
else
{
$values['icons_apple_startup'] = json_encode( $apple );
}
$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'];
@@ -212,65 +208,85 @@ class _icons extends \IPS\Dispatcher\Controller
/**
* Process application icon
*
* @param array $values Values from form submission
* @param array $homeScreen Existing values, if any
* @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() )
public static function processApplicationIcon( $values, $homeScreen = array(), $homeScreenMaskable = array() )
{
if( $values['icons_homescreen'] )
if( $values['icons_homescreen'] or $values['icons_homescreen_maskable'] )
{
$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['icons_homescreen'] );
foreach( $sizes as $filename => $_sizes )
foreach( [ 'icons_homescreen', 'icons_homescreen_maskable' ] as $type )
{
try
$setting = [];
if( $values[ $type ] )
{
$image = \IPS\Image::create( $values['icons_homescreen']->contents() );
if( $image::exifSupported() )
{
$image->setExifData( $values['icons_homescreen']->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
$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),
);
}
catch ( \Exception $e ) { }
}
$values['icons_homescreen'] = json_encode( $setting );
$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
{
@@ -284,6 +300,15 @@ class _icons extends \IPS\Dispatcher\Controller
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'] = '';
}
@@ -1141,7 +1141,6 @@ class _themes extends \IPS\Node\Controller
'set_update_check' => $xml->getAttribute('update_check'),
'set_long_version' => ( $xml->getAttribute('long_version') ) ? $xml->getAttribute('long_version') : \IPS\Application::load('core')->long_version,
'set_is_default' => $set->is_default,
'set_is_acp_default' => $set->is_acp_default,
'set_permissions' => implode( ',', $groups )
) );
@@ -1198,7 +1197,19 @@ class _themes extends \IPS\Node\Controller
$xml->read();
while ( $xml->read() )
{
if ( $xml->name === 'app' )
if ( $xml->name === 'header' )
{
$set->custom_header = $xml->readString();
$set->save();
$xml->next();
}
elseif ( $xml->name === 'footer' )
{
$set->custom_footer = $xml->readString();
$set->save();
$xml->next();
}
elseif ( $xml->name === 'app' )
{
$appKey = $xml->getAttribute('key');
if ( !array_key_exists( $appKey, $data['apps'] ) )
@@ -1767,13 +1778,11 @@ class _themes extends \IPS\Node\Controller
{
throw new \InvalidArgumentException( 'form_bad_value' );
}
$path = ( \IPS\Request::i()->theme_css_group_type == 'existing' ) ? \IPS\Request::i()->theme_css_group_existing : \IPS\Request::i()->theme_css_group_new;
/* Make sure key is unique */
try
{
$row = \IPS\Db::i()->select( '*', 'core_theme_css', array( \IPS\Db::i()->in( 'css_set_id', array( 0, \IPS\Request::i()->id ) ) . " AND css_app=? AND css_path=? AND css_location=? AND css_name=?", \IPS\Request::i()->theme_css_app, $path, \IPS\Request::i()->theme_css_location, $val ) )->first();
$row = \IPS\Db::i()->select( '*', 'core_theme_css', array( \IPS\Db::i()->in( 'css_set_id', array( 0, \IPS\Request::i()->id ) ) . " AND css_app=? AND css_path=? AND css_location=? AND css_name=?", 'core', 'custom', 'front', $val ) )->first();
if ( isset( $row['css_id'] ) )
{
@@ -1787,36 +1796,6 @@ class _themes extends \IPS\Node\Controller
return true;
} ) );
/* Locations */
$locations = iterator_to_array( \IPS\Db::i()->select( 'css_location', 'core_theme_css' )->setKeyField( 'css_location' ) );
unset( $locations['admin'] );
/* Groups */
$groups = iterator_to_array( \IPS\Db::i()->select( 'css_path', 'core_theme_css', NULL, 'css_path asc' )->setKeyField( 'css_path' ) );
/* Apps */
$apps = array();
foreach( \IPS\Application::applications() as $key => $data )
{
$apps[ $key ] = $data->_title;
}
$form->add( new \IPS\Helpers\Form\Select( 'theme_css_location', NULL, FALSE, array( 'options' => $locations ), NULL, NULL, NULL, 'theme_css_location' ) );
$form->add( new \IPS\Helpers\Form\Radio( 'theme_css_group_type', 'existing', FALSE, array(
'options' => array( 'existing' => 'theme_css_group_o_existing',
'new' => 'theme_css_group_o_new' ),
'toggles' => array( 'existing' => array( 'group_existing' ),
'new' => array( 'group_new' ) )
) ) );
$form->add( new \IPS\Helpers\Form\Text( 'theme_css_group_new', NULL, FALSE, array( 'regex' => '/^([A-Z0-9_\\-]+?)?$/i' ), NULL, NULL, NULL, 'group_new' ) );
$form->add( new \IPS\Helpers\Form\Select( 'theme_css_group_existing', 'custom', FALSE, array( 'options' => $groups, 'parse' => 'normal' ), NULL, NULL, NULL, 'group_existing' ) );
$form->add( new \IPS\Helpers\Form\Select( 'theme_css_app', NULL, TRUE, array( 'options' => $apps ) ) );
if( ! \IPS\Request::i()->isAjax() )
{
@@ -1826,9 +1805,9 @@ class _themes extends \IPS\Node\Controller
if ( $values = $form->values() )
{
$id = \intval( \IPS\Request::i()->id );
$save = array( 'app' => $values['theme_css_app'],
'location' => $values['theme_css_location'],
'path' => ( $values['theme_css_group_type'] == 'existing' ) ? $values['theme_css_group_existing'] : $values['theme_css_group_new'],
$save = array( 'app' => 'core',
'location' => 'front',
'path' => 'custom',
'name' => $values['theme_css_name'],
'added_to' => $id,
'set_id' => $id,
@@ -1934,7 +1913,7 @@ class _themes extends \IPS\Node\Controller
if ( ! isset( \IPS\Request::i()->rebuildDone ) )
{
$url = \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=devImportBeforeExport&id=' . $id )->csrf();
$form->addMessage( \IPS\Member::loggedIn()->language()->addToStack('theme_export_designer_mode_build_message', NULL, array( 'sprintf' => array( $url ) ) ), 'ipsMessage ipsMessage_info', FALSE );
$form->addMessage( \IPS\Member::loggedIn()->language()->addToStack( 'theme_export_designer_mode_build_message', FALSE, array( 'sprintf' => array( $url ) ) ), 'ipsMessage ipsMessage_info', FALSE );
}
$form->add( new \IPS\Helpers\Form\Text( 'theme_template_export_author_name', ( $storedAuthor !== null ) ? $storedAuthor['name'] : false, false ) );
@@ -1995,7 +1974,15 @@ class _themes extends \IPS\Node\Controller
$xml->startAttribute('update_check');
$xml->text( $updateCheck );
$xml->endAttribute();
$xml->startElement('header');
$xml->writeCData( str_replace( ']]>', ']]]]><![CDATA[>', $themeSet->custom_header ) );
$xml->endElement();
$xml->startElement('footer');
$xml->writeCData( str_replace( ']]>', ']]]]><![CDATA[>', $themeSet->custom_footer ) );
$xml->endElement();
/* Copy logos */
if( $themeSet->logo_data )
{
@@ -2336,7 +2323,7 @@ class _themes extends \IPS\Node\Controller
try
{
$theme = \IPS\Theme::load( \IPS\Request::i()->id );
if ( $theme->is_default or $theme->is_acp_default )
if ( $theme->is_default )
{
\IPS\Output::i()->error( 'cannot_delete_default_theme', '1C163/3', 403, '' );
}
@@ -2877,8 +2864,21 @@ class _themes extends \IPS\Node\Controller
}
else
{
$theme = \IPS\Theme::load( \IPS\Request::i()->id );
$customVersion = \IPS\Db::i()->select( '*', 'core_theme_templates', array( 'template_id=?', (int) \IPS\Request::i()->t_item_id ) )->first();
$rawTemplates = \IPS\Theme::master()->getRawTemplates( $customVersion['template_app'], $customVersion['template_location'], $customVersion['template_group'], \IPS\Theme::RETURN_ALL, TRUE );
$master = \IPS\Theme::master()->getRawTemplates( $customVersion['template_app'], $customVersion['template_location'], $customVersion['template_group'], \IPS\Theme::RETURN_ALL, TRUE );
$rawTemplates = [];
if ( isset( \IPS\Request::i()->type ) and \IPS\Request::i()->type == 'parent' and $theme->parent() )
{
$rawTemplates = $theme->parent()->getRawTemplates( $customVersion['template_app'], $customVersion['template_location'], $customVersion['template_group'], \IPS\Theme::RETURN_ALL, TRUE );
}
if ( ! isset( $rawTemplates[ $customVersion['template_app'] ][ $customVersion['template_location'] ][ $customVersion['template_group'] ][ $customVersion['template_name'] ] ) )
{
$rawTemplates = $master;
}
$original = isset( $rawTemplates[ $customVersion['template_app'] ][ $customVersion['template_location'] ][ $customVersion['template_group'] ][ $customVersion['template_name'] ] ) ? $rawTemplates[ $customVersion['template_app'] ][ $customVersion['template_location'] ][ $customVersion['template_group'] ][ $customVersion['template_name'] ]['template_content'] : '';
}
@@ -2894,119 +2894,21 @@ class _themes extends \IPS\Node\Controller
{
/* Check permission */
\IPS\Dispatcher::i()->checkAcpPermission( 'theme_templates_manage' );
/* Grab groups */
$id = \intval( \IPS\Request::i()->id );
$t_type = \IPS\Request::i()->t_type ?: 'templates';
$t_app = \IPS\Request::i()->t_app ?: 'core';
$t_location = \IPS\Request::i()->t_location ?: ( ( \IPS\Request::i()->t_app ) ? false : 'front' );
$hasHistory = \IPS\Db::i()->select( 'count(*) as cnt', 'core_theme_content_history', array( 'content_set_id=?', $id ) )->setValueField('cnt')->first();
if ( $id === 1 AND \IPS\IN_DEV OR $hasHistory )
{
\IPS\Output::i()->sidebar['actions'] = array(
'diff' => array(
'icon' => 'cog',
'link' => \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=diff&id=' . $id ),
'title' => 'core_theme_set_diff',
)
);
}
if ( $t_type == 'templates' )
{
$t_group = \IPS\Request::i()->t_group ?: ( ( \IPS\Request::i()->t_app ) ? false : 'global' );
$t_template = \IPS\Request::i()->t_name ?: ( ( \IPS\Request::i()->t_app ) ? false : 'globalTemplate' );
}
else
{
$t_group = \IPS\Request::i()->t_group ?: ( ( \IPS\Request::i()->t_app ) ? false : 'custom' );
$t_template = \IPS\Request::i()->t_name ?: ( ( \IPS\Request::i()->t_app ) ? false : 'custom.css' );
}
$groups = array();
$bitNames = array();
$template = array();
$themeSet = \IPS\Theme::load( $id );
if ( $t_type == 'templates' )
{
$templateNames = $themeSet->getRawTemplates( '', '', '', \IPS\Theme::RETURN_ALL_NO_CONTENT );
$templateBits = $themeSet->getRawTemplates( $t_app, $t_location, $t_group, \IPS\Theme::RETURN_ALL );
$templateBit = ( ! empty( $t_template ) AND !empty( $templateBits[ $t_app ][ $t_location ][ $t_group ][ $t_template ] ) ) ? $templateBits[ $t_app ][ $t_location ][ $t_group ][ $t_template ] : null;
$itemId = $templateBit['template_id'];
}
else
{
$templateNames = $themeSet->getRawCss( '', '', '', \IPS\Theme::RETURN_ALL_NO_CONTENT );
$templateBits = $themeSet->getRawCss( $t_app, $t_location, $t_group, \IPS\Theme::RETURN_ALL );
$templateBit = ( ! empty( $t_template ) ) ? $templateBits[ $t_app ][ $t_location ][ $t_group ][ $t_template ] : null;
$itemId = $templateBit['css_id'];
}
if ( $t_type == 'templates' )
{
/* Remove Admin Templates */
foreach( $templateNames as $app => $items )
{
foreach( $templateNames[ $app ] as $location => $items )
{
if ( $location == 'admin' )
{
unset( $templateNames[ $app ][ $location ] );
}
}
}
}
else
{
if ( $themeSet->by_skin_gen )
{
/* Remove all but custom/custom.css */
foreach( $templateNames as $app => $items )
{
foreach( $templateNames[ $app ] as $location => $items )
{
if ( $location === 'front' )
{
foreach( $templateNames[ $app ][ $location ] as $path => $items )
{
if ( $path != 'custom' )
{
unset( $templateNames[ $app ][ $location ][ $path ] );
}
}
}
else
{
unset( $templateNames[ $app ][ $location ] );
}
}
}
}
else
{
/* Remove Admin Templates */
foreach( $templateNames as $app => $items )
{
foreach( $templateNames[ $app ] as $location => $items )
{
if ( $location == 'admin' )
{
unset( $templateNames[ $app ][ $location ] );
}
}
}
}
}
$id = \intval( \IPS\Request::i()->id );
$themeSet = \IPS\Theme::load( $id );
\IPS\Output::i()->sidebar['actions']['advancedmode'] = array(
'title' => ( \IPS\Settings::i()->theme_editing_advanced_mode == 1 ) ? 'theme_advanced_mode_off' : 'theme_advanced_mode_on',
'icon' => 'paint-brush',
'primary' => \IPS\Settings::i()->theme_editing_advanced_mode == 1,
'link' => \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=advancedEditingToggle&from=' . $id ),
'data' => array( 'confirm' => '', 'confirmSubMessage' => ( \IPS\Settings::i()->theme_editing_advanced_mode ) ? \IPS\Member::loggedIn()->language()->addToStack( 'theme_advanced_mode_submessage_off' ) : \IPS\Member::loggedIn()->language()->addToStack( 'theme_advanced_mode_submessage_on' ) )
);
/* Display */
\IPS\Output::i()->responsive = FALSE;
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('theme_editing_title', FALSE, array( 'sprintf' => array( $themeSet->_title ) ) );
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'codemirror/diff_match_patch.js', 'core', 'interface' ) );
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'codemirror/codemirror.js', 'core', 'interface' ) );
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'codemirror/codemirror.css', 'core', 'interface' ) );
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'customization/templates.css', 'core', 'admin' ) );
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'admin_templates.js', 'core' ) );
@@ -3015,19 +2917,246 @@ class _themes extends \IPS\Node\Controller
/* Display */
\IPS\Output::i()->breadcrumb = array(
array(
\IPS\Http\Url::internal('app=core&module=customization&controller=themes'),
'menu__' . \IPS\Dispatcher::i()->application->directory . '_' . \IPS\Dispatcher::i()->module->key
),
array(
NULL,
\IPS\Member::loggedIn()->language()->addToStack( $name )
)
);
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'customization' )->templateEditor( $themeSet, $templateNames, $templateBit, array( 'app' => $t_app, 'location' => $t_location, 'group' => $t_group, 'template' => $t_template, 'type' => $t_type, 'item_id' => $itemId ) );
array(
\IPS\Http\Url::internal('app=core&module=customization&controller=themes'),
'menu__' . \IPS\Dispatcher::i()->application->directory . '_' . \IPS\Dispatcher::i()->module->key
),
array(
NULL,
\IPS\Member::loggedIn()->language()->addToStack( $name )
)
);
/* Are we using the very lovely simple mode? */
if ( ! \IPS\Settings::i()->theme_editing_advanced_mode )
{
$form = new \IPS\Helpers\Form( 'form', 'theme_simple_save_all' );
$form->hiddenValues['id'] = $id;
$form->class = 'ipsForm_vertical';
$customCss = $themeSet->getRawCss( NULL, 'front', 'custom', \IPS\Theme::RETURN_ALL );
$cssData = NULL;
$itemId = NULL;
$cssFiles = 0;
foreach( $customCss as $app => $data )
{
if ( isset( $customCss[ $app ]['front']['custom'] ) )
{
foreach ( $customCss[ $app ]['front']['custom'] as $name => $data )
{
$cssFiles++;
if ( $name == 'custom.css' )
{
$itemId = $data['css_id'];
$cssData .= "\n" . $data['css_content'];
}
else
{
$cssData .= "\n\n/* ===[Filename:" . $app . '/custom/' . $name . "]=== /*\n\n" . $data['css_content'];
}
}
}
}
$form->addTab('theme_simple_header_tab');
$form->add( new \IPS\Helpers\Form\TextArea( 'theme_simple_header', $themeSet->custom_header, FALSE, [ 'mode' => 'htmlmixed', 'height' => 800 ] ) );
$form->addTab('theme_simple_footer_tab');
$form->add( new \IPS\Helpers\Form\TextArea( 'theme_simple_footer', $themeSet->custom_footer, FALSE, [ 'mode' => 'htmlmixed', 'height' => 800 ] ) );
$form->addTab('theme_simple_css_tab');
if ( $cssFiles > 1 )
{
$form->addMessage( 'theme_simple_lots_of_css_files', 'ipsMessage ipsMessage_warning' );
}
$form->add( new \IPS\Helpers\Form\TextArea( 'theme_simple_css', $cssData, FALSE, [ 'mode' => 'css', 'height' => 800 ] ) );
if ( $values = $form->values() )
{
if ( \IPS\Request::i()->isAjax() )
{
/* Test the syntax */
$errors = [];
foreach( [ 'header', 'footer', 'css' ] as $key )
{
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') ] ] );
}
}
if ( \count( $errors ) )
{
\IPS\Output::i()->error( implode( ". ", $errors ), '3C163/I', 500, '' );
}
}
$themeSet->custom_header = $values['theme_simple_header'];
$themeSet->custom_footer = $values['theme_simple_footer'];
$themeSet->save();
/* Now remove any additional CSS files */
if ( $cssFiles > 1 )
{
foreach( $customCss as $app => $data )
{
if ( isset( $customCss[ $app ]['front']['custom'] ) )
{
foreach ( $customCss[ $app ]['front']['custom'] as $name => $data )
{
if ( ! ( $app == 'core' and $name == 'custom.css' ) )
{
\IPS\Theme::removeCss( $app, 'front', 'custom', NULL, TRUE, $name );
}
}
}
}
}
/* Finally, save the theme */
$themeSet->saveCss( [
'app' => 'core',
'location' => 'front',
'group' => 'custom',
'name' => 'custom.css',
'content' => $values['theme_simple_css'],
'set_id' => $id,
'item_id' => $itemId
] );
/* Remove compiled templates to ensure the globalTemplate gets the latest updates */
\IPS\Theme::deleteCompiledTemplate( 'core', 'front', 'global', $themeSet->_id );
if( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->json( ['result' => 'ok' ] );
}
else
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=templates&id=' . $id ), 'saved' );
}
}
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'customization' )->templateEditorSimple( $themeSet, $form );
}
else
{
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'codemirror/diff_match_patch.js', 'core', 'interface' ) );
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'codemirror/codemirror.js', 'core', 'interface' ) );
/* Grab groups */
$t_type = \IPS\Request::i()->t_type ?: 'templates';
$t_app = \IPS\Request::i()->t_app ?: 'core';
$t_location = \IPS\Request::i()->t_location ?: ( ( \IPS\Request::i()->t_app ) ? false : 'front' );
$hasHistory = \IPS\Db::i()->select( 'count(*) as cnt', 'core_theme_content_history', array('content_set_id=?', $id) )->setValueField( 'cnt' )->first();
if ( $id === 1 and \IPS\IN_DEV or $hasHistory )
{
\IPS\Output::i()->sidebar['actions']['diff'] = [
'icon' => 'cog',
'link' => \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=diff&id=' . $id ),
'title' => 'core_theme_set_diff'
];
}
if ( $t_type == 'templates' )
{
$t_group = \IPS\Request::i()->t_group ?: ( ( \IPS\Request::i()->t_app ) ? false : 'global' );
$t_template = \IPS\Request::i()->t_name ?: ( ( \IPS\Request::i()->t_app ) ? false : 'globalTemplate' );
}
else
{
$t_group = \IPS\Request::i()->t_group ?: ( ( \IPS\Request::i()->t_app ) ? false : 'custom' );
$t_template = \IPS\Request::i()->t_name ?: ( ( \IPS\Request::i()->t_app ) ? false : 'custom.css' );
}
$groups = array();
$bitNames = array();
$template = array();
if ( $t_type == 'templates' )
{
$templateNames = $themeSet->getRawTemplates( '', '', '', \IPS\Theme::RETURN_ALL_NO_CONTENT );
$templateBits = $themeSet->getRawTemplates( $t_app, $t_location, $t_group, \IPS\Theme::RETURN_ALL );
$templateBit = ( !empty( $t_template ) and !empty( $templateBits[$t_app][$t_location][$t_group][$t_template] ) ) ? $templateBits[$t_app][$t_location][$t_group][$t_template] : null;
$itemId = $templateBit['template_id'];
}
else
{
$templateNames = $themeSet->getRawCss( '', '', '', \IPS\Theme::RETURN_ALL_NO_CONTENT );
$templateBits = $themeSet->getRawCss( $t_app, $t_location, $t_group, \IPS\Theme::RETURN_ALL );
$templateBit = ( !empty( $t_template ) ) ? $templateBits[$t_app][$t_location][$t_group][$t_template] : null;
$itemId = $templateBit['css_id'];
}
if ( $t_type == 'templates' )
{
/* Remove Admin Templates */
foreach ( $templateNames as $app => $items )
{
foreach ( $templateNames[$app] as $location => $items )
{
if ( $location == 'admin' )
{
unset( $templateNames[$app][$location] );
}
}
}
}
else
{
$cssFiles = [];
/* Remove all but custom/custom.css */
foreach ( $templateNames as $app => $items )
{
foreach ( $templateNames[$app] as $location => $items )
{
if ( $location === 'front' )
{
foreach ( $templateNames[$app][$location] as $path => $items )
{
if ( $path == 'custom' )
{
$cssFiles[ $app ][ $location ][ $path ] = $items;
}
}
}
}
}
$templateNames = $cssFiles;
}
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'customization' )->templateEditor( $themeSet, $templateNames, $templateBit, array( 'app' => $t_app, 'location' => $t_location, 'group' => $t_group, 'template' => $t_template, 'type' => $t_type, 'item_id' => $itemId ) );
}
}
/**
* Toggle between the simple and advanced editing modes
*
* @return void
*/
public function advancedEditingToggle()
{
\IPS\Settings::i()->changeValues( [ 'theme_editing_advanced_mode' => ( \IPS\Settings::i()->theme_editing_advanced_mode ) ? 0 : 1 ] );
if ( isset( \IPS\Request::i()->from ) )
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=templates&id=' . \IPS\Request::i()->from ), 'theme_mode_switched' );
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes' ), 'theme_mode_switched' );
}
/**
* Set Members
*
@@ -81,7 +81,7 @@ class _search extends \IPS\Dispatcher\Controller
try
{
$response = $val->request()->get()->decodeJson();
$response = \IPS\Content\Search\Elastic\Index::request( $val )->get()->decodeJson();
}
catch ( \Exception $e )
{
@@ -91,13 +91,25 @@ class _search extends \IPS\Dispatcher\Controller
{
throw new \DomainException('search_elastic_server_no_version');
}
if ( version_compare( $response['version']['number'], \IPS\Content\Search\Elastic\Index::MINIMUM_VERSION, '<' ) )
/* Open search is compatible up to at least version 2 */
if( isset( $response['version']['distribution'] ) and $response['version']['distribution'] == 'opensearch' )
{
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack('search_elastic_server_unsupported_version', FALSE, array( 'sprintf' => array( \IPS\Content\Search\Elastic\Index::MINIMUM_VERSION, $response['version']['number'] ) ) ) );
if ( version_compare( $response['version']['number'], '2.0', '>' ) )
{
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack('search_opensearch_server_unsupported_version', FALSE, array( 'sprintf' => array( $response['version']['number'] ) ) ) );
}
}
if ( version_compare( $response['version']['number'], \IPS\Content\Search\Elastic\Index::UNSUPPORTED_VERSION, '>=' ) )
else
{
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack('search_elastic_server_unsupported_version_high', FALSE, array( 'sprintf' => array( \IPS\Content\Search\Elastic\Index::UNSUPPORTED_VERSION, $response['version']['number'] ) ) ) );
if ( version_compare( $response['version']['number'], \IPS\Content\Search\Elastic\Index::MINIMUM_VERSION, '<' ) )
{
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack('search_elastic_server_unsupported_version', FALSE, array( 'sprintf' => array( \IPS\Content\Search\Elastic\Index::MINIMUM_VERSION, $response['version']['number'] ) ) ) );
}
if ( version_compare( $response['version']['number'], \IPS\Content\Search\Elastic\Index::UNSUPPORTED_VERSION, '>=' ) )
{
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack('search_elastic_server_unsupported_version_high', FALSE, array( 'sprintf' => array( \IPS\Content\Search\Elastic\Index::UNSUPPORTED_VERSION, $response['version']['number'] ) ) ) );
}
}
}, NULL, NULL, 'search_elastic_server' ) );
\IPS\Member::loggedIn()->language()->words['search_elastic_server_desc'] = sprintf( \IPS\Member::loggedIn()->language()->get('search_elastic_server_desc'), \IPS\Content\Search\Elastic\Index::MINIMUM_VERSION, \IPS\Content\Search\Elastic\Index::UNSUPPORTED_VERSION );
@@ -179,6 +191,11 @@ class _search extends \IPS\Dispatcher\Controller
$form->add( new \IPS\Helpers\Form\Number( 'search_index_timeframe', \IPS\Settings::i()->search_index_timeframe, FALSE, array( 'unlimited' => 0, 'unlimitedLang' => 'search_index_timeframe_unlimited' ), NULL, \IPS\Member::loggedIn()->language()->addToStack('search_index_timeframe_prefix'), \IPS\Member::loggedIn()->language()->addToStack('search_index_timeframe_suffix'), 'search_index_timeframe' ) );
$form->add( new \IPS\Helpers\Form\Number( 'search_index_maxresults', \IPS\Settings::i()->search_index_maxresults, FALSE, array(), NULL, NULL, NULL, 'search_index_maxresults' ) );
$form->addHeader('search_logs');
$groups = array_combine( array_keys( \IPS\Member\Group::groups( TRUE, TRUE ) ), array_map( function( $_group ) { return (string) $_group; }, \IPS\Member\Group::groups( TRUE, TRUE ) ) );
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'searchlog_exclude_groups', json_decode( \IPS\Settings::i()->searchlog_exclude_groups, TRUE ), FALSE, array( 'options' => $groups, 'multiple' => true ), NULL, NULL, NULL, 'searchlog_exclude_groups' ) );
if ( $values = $form->values() )
{
$engine = \IPS\Settings::i()->search_method;
@@ -210,7 +227,9 @@ class _search extends \IPS\Dispatcher\Controller
\IPS\Log::log( $e, 'search_index_prune' );
}
}
$values['searchlog_exclude_groups'] = json_encode( $values['searchlog_exclude_groups'] );
/* Go ahead and save... */
$form->saveAsSettings( $values );
\IPS\Session::i()->log( 'acplogs__search_settings' );
@@ -161,6 +161,10 @@ class _streams extends \IPS\Node\Controller
$form = new \IPS\Helpers\Form;
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'all_activity_extra_stream', $currentValuesStream, FALSE, array( 'options' => $options ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'activity_stream_rss', \IPS\Settings::i()->activity_stream_rss ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'activity_stream_subscriptions', \IPS\Settings::i()->activity_stream_subscriptions, FALSE, ['togglesOn' => ['activity_stream_subscriptions_max', 'activity_stream_subscriptions_inactive_limit' ] ] ) );
$form->add( new \IPS\Helpers\Form\Number( 'activity_stream_subscriptions_max', \IPS\Settings::i()->activity_stream_subscriptions_max, TRUE, ['min' => 1, 'max' => \IPS\CIC ? 10:NULL ], NULL, NULL, NULL,'activity_stream_subscriptions_max' ) );
$form->add( new \IPS\Helpers\Form\Number( 'activity_stream_subscriptions_inactive_limit', \IPS\Settings::i()->activity_stream_subscriptions_inactive_limit, TRUE, [ ], NULL, NULL, NULL,'activity_stream_subscriptions_inactive_limit' ) );
if ( $values = $form->values() )
{
@@ -171,6 +175,8 @@ class _streams extends \IPS\Node\Controller
}
$toSave[ 'activity_stream_rss' ] = $values['activity_stream_rss'];
$toSave[ 'activity_stream_subscriptions' ] = $values['activity_stream_subscriptions'];
$toSave[ 'activity_stream_subscriptions_max' ] = $values['activity_stream_subscriptions_max'];
$form->saveAsSettings( $toSave );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=discovery&controller=streams' ), 'saved' );
@@ -0,0 +1,96 @@
<?php
/**
* @brief Editor Stored Replies aka Stock Actions aka whatever else I rename it during this coding session
* @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
* @subpackage Code
* @since 03 September 2021
*/
namespace IPS\core\modules\admin\editor;
/* 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;
}
/**
* Stored Replies
*/
class _storedreplies extends \IPS\Node\Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* Node Class
*/
protected $nodeClass = 'IPS\core\StoredReplies';
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'stored_replies_manage' );
parent::execute();
}
/**
* Manage
*
* @return void
*/
public function manage()
{
/* Have we not dragged the button to the editor bar? */
$toolbars = json_decode( \IPS\Settings::i()->ckeditor_toolbars, true );
$found = 0;
foreach ( array('desktop', 'tablet', 'phone') as $device )
{
foreach ( $toolbars[$device] as $row )
{
if ( \is_array( $row ) and isset( $row['items'] ) )
{
if ( \in_array( 'ipsstockreplies', $row['items'] ) )
{
$found++;
}
}
}
}
/* Do we have any stored replies yet? */
if ( \IPS\Db::i()->select('count(*)', 'core_editor_stored_replies' )->first() )
{
if ( $found > 0 and $found < 3 )
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global', 'core' )->message( 'editor_stored_replies_not_all_editors_have_button', 'info' );
}
else
{
if ( $found == 0 )
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global', 'core' )->message( 'editor_stored_replies_no_editor_has_button', 'warning' );
}
}
}
else
{
if ( $found )
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global', 'core' )->message( 'editor_stored_replies_none_created_button', 'info' );
}
}
parent::manage();
}
}
@@ -57,6 +57,7 @@ class _toolbar extends \IPS\Dispatcher\Controller
'ipssource',
'ipsspoiler',
'ipsgiphy',
'ipsstockreplies',
'justify',
'lineutils',
'list',
@@ -203,12 +204,25 @@ class _toolbar extends \IPS\Dispatcher\Controller
/* Custom Plugin */
if ( preg_match( '/^custom-(\w{32})$/i', $button, $matches ) and !\IPS\IN_DEV and !\IPS\NO_WRITES )
{
$code = file_get_contents( \IPS\ROOT_PATH . "/applications/core/interface/ckeditor/ckeditor/plugins/{$matches[0]}/plugin.js" );
$option = file_exists( \IPS\ROOT_PATH . "/applications/core/interface/ckeditor/ckeditor/plugins/{$matches[0]}/dialogs/{$matches[0]}.js" );
if ( $option )
if ( \IPS\CIC2 )
{
$code = file_get_contents( \IPS\ROOT_PATH . "/applications/core/interface/ckeditor/ckeditor/plugins/{$matches[0]}/dialogs/{$matches[0]}.js" );
$code = file_get_contents( \IPS\SITE_FILES_PATH . "/ckeditor_custom/{$matches[0]}/plugin.js" );
$option = file_exists( \IPS\SITE_FILES_PATH . "/ckeditor_custom/{$matches[0]}/dialogs/{$matches[0]}.js" );
if ( $option )
{
$code = file_get_contents( \IPS\SITE_FILES_PATH . "/ckeditor_custom/{$matches[0]}/dialogs/{$matches[0]}.js" );
}
}
else
{
$code = file_get_contents( \IPS\ROOT_PATH . "/applications/core/interface/ckeditor/ckeditor/plugins/{$matches[0]}/plugin.js" );
$option = file_exists( \IPS\ROOT_PATH . "/applications/core/interface/ckeditor/ckeditor/plugins/{$matches[0]}/dialogs/{$matches[0]}.js" );
if ( $option )
{
$code = file_get_contents( \IPS\ROOT_PATH . "/applications/core/interface/ckeditor/ckeditor/plugins/{$matches[0]}/dialogs/{$matches[0]}.js" );
}
}
$type = 'inline';
@@ -900,7 +914,7 @@ JS;
$dir = \IPS\ROOT_PATH . '/applications/core/interface/ckeditor/ckeditor/plugins/' . $key;
if ( is_dir( $dir ) )
{
$this->_recursiveDelete( $dir );
$this->_recursiveRmDir( $dir );
}
/* Remove from plugin list */
@@ -924,33 +938,6 @@ JS;
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=editor&controller=toolbar' ), 'saved' );
}
/**
* Recursive delete
*
* @param string $dir Directory
* @return void
*/
protected function _recursiveDelete( $dir )
{
foreach ( new \DirectoryIterator( $dir ) as $file )
{
if ( !$file->isDot() )
{
if ( $file->isDir() )
{
$this->_recursiveDelete( $file->getPathname() );
}
else
{
unlink( $file->getPathname() );
}
}
}
$handle = opendir( $dir );
closedir ( $handle );
rmdir( $dir );
}
/**
* Revert Editor back to default
*
@@ -968,7 +955,11 @@ JS;
{
if ( \IPS\CIC2 )
{
\IPS\Cicloud\folderDelete( "ckeditor_custom/{$key}" );
if ( is_dir( \IPS\SITE_FILES_PATH . '/ckeditor_custom/' . $key ) )
{
/* If this is cloud we only need the key */
$this->_recursiveRmDir( $key );
}
}
else
{
@@ -184,7 +184,7 @@ class _marketplace extends \IPS\Dispatcher\Controller
$file = $this->_apiWrapper( "marketplace/files/" . (int) \IPS\Request::i()->id, [ 'termFields' => 1 ], TRUE );
/* Check purchase status */
if( $file['isPaid'] AND empty( $file['isPurchased'] ) )
if( $file['isPaid'] AND empty( $file['isPurchased'] ) AND !$file['canDownload'] )
{
\IPS\Output::i()->error( 'marketplace_resource_not_purchased', '2C171/A', 403, '' );
}
@@ -523,7 +523,8 @@ class _marketplace extends \IPS\Dispatcher\Controller
'q' => \IPS\Request::i()->title,
'type' => 'downloads_file',
'search_in' => 'titles',
'doNotTrack' => TRUE
'doNotTrack' => TRUE,
'search_and_or' => 'and'
];
$resourceTypeCategories = $this->_resourceTypeCategories();
@@ -135,6 +135,7 @@ class _groups extends \IPS\Dispatcher\Controller
$return['copy'] = array(
'icon' => 'files-o',
'title' => 'copy',
'data' => array( 'confirm' => '', 'confirmMessage' => \IPS\Member::loggedIn()->language()->addToStack( 'group_clone_confirm_message' ), 'confirmSubMessage' => \IPS\Member::loggedIn()->language()->addToStack( 'group_clone_confirm_sub_message' ) ),
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=groups&do=copy&id=' . $row['g_id'] )->csrf(),
);
}
@@ -148,7 +149,7 @@ class _groups extends \IPS\Dispatcher\Controller
);
}
if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'members', 'groups_delete' ) AND !\in_array( $row['g_id'], array( \IPS\Settings::i()->guest_group, \IPS\Settings::i()->member_group, \IPS\Settings::i()->admin_group ) ) )
if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'members', 'groups_delete' ) AND \IPS\Member\Group::load($row['g_id'] )->canDelete() )
{
$return['delete'] = array(
'icon' => 'times-circle',
@@ -279,6 +280,7 @@ class _groups extends \IPS\Dispatcher\Controller
$form->addTab( '__app_' . $class::$permApp );
$matrix = new \IPS\Helpers\Form\Matrix( $class );
$matrix->classes = array( 'cGroupPermissions' );
$matrix->styledRowTitle = TRUE;
$matrix->showTooltips = TRUE;
/* Remove delete buttons and add row button */
@@ -286,9 +288,24 @@ class _groups extends \IPS\Dispatcher\Controller
/* Call the callable callbacks */
$matrix->columns = array(
'label' => function( $key, $value, $data )
'label' => function( $key, $value, $data ) use ( $class )
{
return $value;
$nodeId = explode( '[', $key )[0];
$acpUrl = NULL;
/* Let's try to get a link to the node's permissions */
try
{
$node = $class::load( $nodeId );
if ( !$node->canManagePermissions() )
throw new \DomainException;
$acpUrl = $node->acpUrl( 'permissions' );
}
catch ( \Exception $e ) { }
return \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->titleWithLink( $value, $acpUrl, 'all_permissions', \IPS\Member::loggedIn()->language()->addToStack( "all_permissions_node_hovertitle", FALSE, array( 'sprintf' => [ $value ] ) ) );
}
);
$matrix->widths = array( 'label' => 30 );
@@ -416,6 +433,10 @@ class _groups extends \IPS\Dispatcher\Controller
try
{
$group = \IPS\Member\Group::load( \IPS\Request::i()->id );
if( !$group->canDelete() )
{
\IPS\Output::i()->error( 'cannot_delete_group', '2C108/5', 403, '' );
}
$count = \IPS\Db::i()->select( 'COUNT(*)', 'core_members', array( 'member_group_id=?', $group->g_id ) )->first();
/* Any members in it? */
@@ -41,14 +41,22 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
/* Some advanced search links may bring us here */
\IPS\Output::i()->bypassCsrfKeyCheck = true;
$forceIndex = 'joined';
/* Yeah, this is the level of hackery we've gotten to. Millions of rows in core_members is hard work */
if ( isset( \IPS\Request::i()->filter ) and \IPS\Request::i()->filter == 'members_filter_validating' )
{
$forceIndex = NULL;
}
/* Create the table */
$table = new \IPS\Helpers\Table\Db( 'core_members', \IPS\Http\Url::internal( 'app=core&module=members&controller=members' ), array( array( 'email<>?', '' ) ), 'joined' );
$table = new \IPS\Helpers\Table\Db( 'core_members', \IPS\Http\Url::internal( 'app=core&module=members&controller=members' ), array( array( 'email<>?', '' ) ), $forceIndex );
$table->langPrefix = 'members_';
/* Columns we need */
$table->include = array( 'photo', 'name', 'email', 'joined', 'group_name' );
if ( \IPS\core\Achievements\Rank::getStore() )
if ( \IPS\Settings::i()->achievements_enabled AND \IPS\core\Achievements\Rank::getStore() )
{
$table->include[] = 'achievements_points';
$table->widths['achievements_points'] = '17';
@@ -153,20 +161,26 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
array(
'select' => 'm.row_id',
'from' => array( 'core_admin_permission_rows', 'm' ),
'where' => "m.row_id=core_members.member_id AND m.row_id_type='member'" ),
array(
'select' => 'g.row_id',
'from' => array( 'core_admin_permission_rows', 'g' ),
'where' => array( 'g.row_id', \IPS\Db::i()->select( 'row_id', array( 'core_admin_permission_rows', 'sub' ), array( "((sub.row_id=core_members.member_group_id OR FIND_IN_SET( sub.row_id, core_members.mgroup_others ) ) AND sub.row_id_type='group') AND g.row_id_type='group'" ), NULL, array( 0, 1 ) ) ) )
'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] );
unset( $table->joins[3] );
}
if ( $table->filter !== 'members_filter_validating' )
{
unset( $table->joins[0] );
}
if ( ! isset( \IPS\Request::i()->advanced_search_submitted ) )
{
unset( $table->joins[1] );
}
/* Ranks */
$rankOptions = array( '' => 'any' );
foreach( \IPS\core\Achievements\Rank::getStore() as $rank )
@@ -256,7 +270,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
'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' => '( m.row_id IS NOT NULL OR g.row_id 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() )
@@ -272,9 +286,25 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
/* Custom parsers */
$table->parsers = array(
'email' => function( $val, $row )
'email' => function( $val, &$row )
{
if ( $row['vid'] )
if ( ! array_key_exists( 'vid', $row ) )
{
/* Grab the data if need be */
$member = \IPS\Member::constructFromData( $row );
if ( $member->members_bitoptions['validating'] )
{
try
{
$validating = \IPS\Db::i()->select( '*', 'core_validating', [ 'member_id=?', $member->member_id ] )->first();
$row = array_merge( $validating, $row );
}
catch( \UnderflowException $e ) { }
}
}
if ( ! empty( $row['vid'] ) )
{
return \IPS\Theme::i()->getTemplate( 'members', 'core', 'admin' )->memberEmailCell( \IPS\Theme::i()->getTemplate( 'members', 'core', 'admin' )->memberValidatingCell( $val, \IPS\Member::constructFromData( $row )->validatingDescription( $row ) ) );
}
@@ -384,7 +414,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$return['approve'] = array(
'icon' => 'check-circle',
'title' => 'approve',
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=approve&id=' . $member->member_id )->csrf(),
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=approve&id=' . $member->member_id . $this->getSafeUrlFromFilters() )->csrf(),
'id' => "{$member->member_id}-approve",
'data' => array(
'bubble' => '',
@@ -393,7 +423,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$return['ban'] = array(
'icon' => 'times',
'title' => 'ban',
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=ban&id=' . $member->member_id . '&permban=1' )->csrf(),
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=ban&id=' . $member->member_id . '&permban=1' . $this->getSafeUrlFromFilters() )->csrf(),
'id' => "{$member->member_id}-ban",
'data' => array(
'bubble' => '',
@@ -405,7 +435,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$return['resend_email'] = array(
'icon' => 'envelope-o',
'title' => 'resend_validation_email',
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=resendEmail&id=' . $member->member_id )->csrf(),
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=resendEmail&id=' . $member->member_id . $this->getSafeUrlFromFilters() )->csrf(),
'data' => array( 'doajax' => '' ),
'id' => "{$member->member_id}-resend",
);
@@ -423,7 +453,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$return['unlock'] = array(
'icon' => 'unlock',
'title' => 'unlock',
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=unlock&id=' . $member->member_id )->csrf(),
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=unlock&id=' . $member->member_id . $this->getSafeUrlFromFilters() )->csrf(),
'data' => array( 'bubble' => '' )
);
}
@@ -431,7 +461,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$return['view'] = array(
'icon' => 'search',
'title' => 'view',
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=view&id=' ) . $member->member_id,
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=view&id=' . $member->member_id . $this->getSafeUrlFromFilters() ),
);
if ( !\IPS\Request::i()->_groupFilter )
@@ -443,7 +473,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$return['flag'] = array(
'icon' => 'flag',
'title' => 'spam_flag',
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=spam&id=' . $member->member_id . '&status=1' )->csrf(),
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=spam&id=' . $member->member_id . '&status=1' . $this->getSafeUrlFromFilters() )->csrf(),
'hidden' => $member->members_bitoptions['bw_is_spammer'],
'id' => "{$member->member_id}-flag",
'data' => array(
@@ -453,7 +483,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$return['unflag'] = array(
'icon' => 'flag ipsType_spammer',
'title' => 'spam_unflag',
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=spam&id=' . $member->member_id . '&status=0' )->csrf(),
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=spam&id=' . $member->member_id . '&status=0' . $this->getSafeUrlFromFilters() )->csrf(),
'hidden' => !$member->members_bitoptions['bw_is_spammer'],
'id' => "{$member->member_id}-unflag",
'data' => array(
@@ -468,7 +498,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$return['delete'] = array(
'icon' => 'times-circle',
'title' => 'delete',
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=delete&id=' ) . $member->member_id,
'link' => \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=delete&id=' . $member->member_id . $this->getSafeUrlFromFilters() ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack( 'delete_member', FALSE, array( 'sprintf' => $member->name ) ) ),
);
}
@@ -569,6 +599,25 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
\IPS\Output::i()->output .= (string) $table;
}
protected function toggleDataLayerPii()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'member_edit' );
$member = \IPS\Member::load( \IPS\Request::i()->id );
if ( $member->isAdmin() )
{
\IPS\Dispatcher::i()->checkAcpPermission( 'member_edit_admin' );
}
if ( $member->member_id )
{
$member->datalayer_collect_pii = (bool) !$member->datalayer_collect_pii;
$member->save();
}
$_SESSION['member_datalayer_changed'] = 1;
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=members&controller=members&do=view&id=' . \IPS\Request::i()->id ?? 0 ) );
}
/**
* Prune members
*
@@ -1180,6 +1229,12 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$history = \IPS\Theme::i()->getTemplate('memberprofile')->history( $member, $history, $historyFilters );
}
if ( isset( $_SESSION['member_datalayer_changed'] ) )
{
\IPS\Output::i()->inlineMessage = 'Saved';
unset( $_SESSION['member_datalayer_changed'] );
}
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'members/view.css', 'core', 'admin' ) );
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'admin_members.js', 'core' ) );
@@ -1343,7 +1398,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$form = new \IPS\Helpers\Form;
// This looks weird, but we only want this showing here, and not anywhere else 'password' is used as a form key.
\IPS\Member::loggedIn()->language()->words['password_desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'force_password_reset_member', NULL, array( 'sprintf' => array( $member->acpUrl()->csrf()->setQueryString( 'do', 'forcePassReset' ), \IPS\Member::loggedIn()->language()->addToStack( 'force_password_reset_member_confirmmsg' ) ) ) );
\IPS\Member::loggedIn()->language()->words['password_desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'force_password_reset_member', FALSE, array( 'sprintf' => array( $member->acpUrl()->csrf()->setQueryString( 'do', 'forcePassReset' ), \IPS\Member::loggedIn()->language()->addToStack( 'force_password_reset_member_confirmmsg' ) ) ) );
$form->add( new \IPS\Helpers\Form\Password( 'password', '', TRUE, array( 'protect' => TRUE, 'confirm' => 'password_confirm', 'showMeter' => \IPS\Settings::i()->password_strength_meter, 'minimumStrength' => 1, 'bypassProfanity' => \IPS\Helpers\Form\Text::BYPASS_PROFANITY_ALL ) ) );
$form->add( new \IPS\Helpers\Form\Password( 'password_confirm', '', TRUE, array( 'protect' => TRUE, 'bypassProfanity' => \IPS\Helpers\Form\Text::BYPASS_PROFANITY_ALL ) ) );
@@ -1470,7 +1525,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
/* If we have external login handlers, we need to show an interstitual page indicating that with caveats. */
if ( \count( $externals ) )
{
$msg = \IPS\Member::loggedIn()->language()->addToStack( 'force_password_reset_member_external', NULL, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->formatList( $externals ) ) ) );
$msg = \IPS\Member::loggedIn()->language()->addToStack( 'force_password_reset_member_external', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->formatList( $externals ) ) ) );
$form = new \IPS\Helpers\Form( 'form', 'reset_password' );
$form->hiddenValues['id'] = $member->member_id;
$form->addMessage( $msg, 'ipsMessage ipsMessage_warning' );
@@ -2940,6 +2995,26 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
\IPS\Output::i()->output = $form;
}
/**
* Return a partial query string based on any filters or sort options
*
* @return string
*/
protected function getSafeUrlFromFilters(): string
{
$extraUrlParams = [];
foreach( [ 'filter', 'sortby', 'sortdirection' ] as $param )
{
if ( isset( \IPS\Request::i()->$param ) )
{
$extraUrlParams[ $param ] = \IPS\Request::i()->$param;
}
}
return \count( $extraUrlParams ) ? '&' . http_build_query( $extraUrlParams ) : '';
}
/**
* Delete Member
*
@@ -2990,7 +3065,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
/* Handle Submissions */
if ( $values = $form->values() )
{
\IPS\Session::i()->log( 'acplog__members_deleted', array( $member->name => FALSE ) );
\IPS\Session::i()->log( 'acplog__members_deleted_id', array( $member->name => FALSE, $member->member_id => FALSE ) );
switch( $values['member_deletion_content'] )
{
@@ -3007,7 +3082,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
break;
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal("app=core&module=members&controller=members"), 'deleted' );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal("app=core&module=members&controller=members" . $this->getSafeUrlFromFilters() ), 'deleted' );
}
/* Display Form */
@@ -3841,8 +3916,8 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
\IPS\Output::i()->parseFileObjectUrls( $csv );
$csv = \IPS\Output::i()->replaceEmojiWithImages( $csv );
/* Excel requires this for non-ASCII characters to show properly */
$csv = \chr(255) . \chr(254) . mb_convert_encoding( $csv, 'UTF-16LE', 'UTF-8' );
/* Excel requires a BOM this for non-ASCII characters to show properly */
$csv = \chr(0xEF) . \chr(0xBB) . \chr(0xBF) . $csv;
\IPS\Session::i()->log( 'acplog__exported_member_list' );
\IPS\Output::i()->sendOutput( $csv, 200, 'text/csv', array( 'Content-Disposition' => \IPS\Output::getContentDisposition( 'attachment', \IPS\Member::loggedIn()->language()->get('member_pl') . '.csv' ) ), FALSE, FALSE, FALSE, FALSE );
@@ -3886,6 +3961,8 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
'last_post' => 'last_post',
'birthday' => 'import_birthday',
'allow_admin_mails' => 'import_allow_admin_mails',
'achievements_points' => 'achievement_points',
'member_rank' => "members_member_rank_id",
);
if ( \IPS\Settings::i()->signatures_enabled )
{
@@ -4072,7 +4149,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
}
/* Write */
fputcsv( $fh, $dataToWrite, "\t" );
fputcsv( $fh, $dataToWrite );
}
/* Close and loop */
@@ -4176,6 +4253,16 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$valueToWrite = '';
}
break;
case 'member_rank':
if( $rank = \IPS\core\Achievements\Rank::fromPoints( $member['achievements_points'] ) )
{
$valueToWrite = \IPS\Member::loggedIn()->language()->get( 'core_member_rank_' . $rank->_id );
}
else
{
$valueToWrite = '';
}
break;
default:
if ( mb_substr( $column, 0, 7 ) == 'pfield_' )
@@ -4263,7 +4350,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
$wizardData['file'] = tempnam( \IPS\TEMP_DIRECTORY, 'IPS' );
$fh = fopen( $wizardData['file'], 'w' );
fputcsv( $fh, $this->_getExportCsvHeaders( $wizardData ), "\t" );
fputcsv( $fh, $this->_getExportCsvHeaders( $wizardData ) );
fclose( $fh );
return $wizardData;
@@ -4559,7 +4646,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
try
{
$recognize = \IPS\core\Achievements\Recognize::load( $row['recognize'] );
return \IPS\Member::loggedIn()->language()->addToStack( 'acp_badge_from_recognize', NULL, [ 'sprintf' => [ $recognize->content()->url(), $recognize->content()->indefiniteArticle() ] ] );
return \IPS\Member::loggedIn()->language()->addToStack( 'acp_badge_from_recognize', FALSE, [ 'sprintf' => [ $recognize->content()->url(), $recognize->content()->indefiniteArticle() ] ] );
}
catch( \Exception $e )
{
@@ -4628,7 +4715,7 @@ class _members extends \IPS\Helpers\CoverPhoto\Controller
{
return $node->manually_awarded;
},
'disabledIds' => iterator_to_array( \IPS\Db::i()->select( 'id', 'core_member_badges', [ 'member=?', $member->member_id ] ) ),
'disabledIds' => iterator_to_array( \IPS\Db::i()->select( 'badge', 'core_member_badges', [ '`member`=?', $member->member_id ] ) ),
'url' => $member->acpUrl()->setQueryString('do', 'addBadge')
] ) );
@@ -182,7 +182,7 @@ class _notifications extends \IPS\Dispatcher\Controller
$form->addMessage( $option['description'] );
}
if ( !\in_array( 'inline', $option['disabled'] ) or ( ( \IPS\Settings::i()->mobile_app_id || \IPS\Notification::webPushEnabled() ) and !\in_array( 'push', $option['disabled'] ) ) or !\in_array( 'email', $option['disabled'] ) )
if ( !\in_array( 'inline', $option['disabled'] ) or ( \IPS\Notification::webPushEnabled() and !\in_array( 'push', $option['disabled'] ) ) or !\in_array( 'email', $option['disabled'] ) )
{
$canEditField = new \IPS\Helpers\Form\YesNo( 'notificationsettings_editable_' . $key, $defaultConfiguration[ $key ]['editable'], FALSE, array(
'togglesOn' => array( "member_notifications_{$key}_inline_editable", "member_notifications_{$key}_push", "member_notifications_{$key}_email_editable" ),
@@ -281,7 +281,7 @@ class _notifications extends \IPS\Dispatcher\Controller
}
}
if ( !\in_array( 'inline', $option['disabled'] ) or ( ( \IPS\Settings::i()->mobile_app_id || \IPS\Notification::webPushEnabled() ) and !\in_array( 'push', $option['disabled'] ) ) or !\in_array( 'email', $option['disabled'] ) )
if ( !\in_array( 'inline', $option['disabled'] ) or ( \IPS\Notification::webPushEnabled()and !\in_array( 'push', $option['disabled'] ) ) or !\in_array( 'email', $option['disabled'] ) )
{
$row = array(
'notification_key' => $key,
@@ -296,7 +296,7 @@ class _notifications extends \IPS\Dispatcher\Controller
{
if ( $k === 'push' )
{
if ( !\IPS\Settings::i()->mobile_app_id && !\IPS\Notification::webPushEnabled() )
if ( !\IPS\Notification::webPushEnabled() )
{
continue;
}
@@ -373,9 +373,6 @@ class _notifications extends \IPS\Dispatcher\Controller
$extension->resetExtra();
}
}
/* Sounds is special */
\IPS\Db::i()->update( 'core_members', 'members_bitoptions2 = members_bitoptions2 ' . ( !\IPS\Settings::i()->notification_prefs_sound ? '|' : '&~' ) . \IPS\Member::$bitOptions['members_bitoptions']['members_bitoptions2']['disable_notification_sounds'] );
/* Log and redirect */
\IPS\Session::i()->log( 'acplog__notification_settings_existing' );
@@ -74,22 +74,33 @@ class _profilesettings extends \IPS\Dispatcher\Controller
throw new \DomainException('user_name_length_no_match');
}
} ) );
$form->add( new \IPS\Helpers\Form\Custom( 'username_characters', \IPS\Settings::i()->username_characters, FALSE, array(
'getHtml' => function( $field ) {
$easy = NULL;
if ( preg_match( '/^\/\^\(\(\[(\\\p\{L\}\\\p\{M\}|A\-Za\-z)(\\\p\{N\}|0-9)?(.+)?\]\+\)( )?\?\)\+\$\/u$/', $field->value, $matches ) )
if ( \is_array( $field->value ) )
{
$easy['letters'] = ( $matches[1] == '\\p{L}\\p{M}' ) ? 'all' : 'latin';
if ( $matches[2] )
$easy['letters'] = $field->value['letters'];
$easy['numbers'] = $field->value['numbers'];
$easy['spaces'] = (bool) isset( $field->value['spaces'] );
$easy['extra'] = isset( $field->value['extra_enabled'] ) ? $field->value['extra'] : '';
$field->value = $field->value['regex'];
}
else{
if ( preg_match( '/^\/\^\(\(\[(\\\p\{L\}\\\p\{M\}|A\-Za\-z)(\\\p\{N\}|0-9)?(.+)?\]\+\)( )?\?\)\+\$\/u$/', $field->value, $matches ) )
{
$easy['numbers'] = ( $matches[2] == '\\p{N}' ) ? 'all' : 'arabic';
$easy['letters'] = ( $matches[1] == '\\p{L}\\p{M}' ) ? 'all' : 'latin';
if ( $matches[2] )
{
$easy['numbers'] = ( $matches[2] == '\\p{N}' ) ? 'all' : 'arabic';
}
else
{
$easy['numbers'] = 'none';
}
$easy['spaces'] = (bool) isset( $matches[4] );
$easy['extra'] = stripslashes( $matches[3] );
}
else
{
$easy['numbers'] = 'none';
}
$easy['spaces'] = (bool) isset( $matches[4] );
$easy['extra'] = stripslashes( $matches[3] );
}
return \IPS\Theme::i()->getTemplate('members')->usernameRegexSetting( $field->name, $field->value, $easy );
}
@@ -366,7 +366,7 @@ class _reputation extends \IPS\Node\Controller
\IPS\Lang::deleteCustom( 'core', 'core_reputation_level_' . \IPS\Request::i()->id );
}
catch ( \UnderflowExceptiobn $e ) { }
catch ( \UnderflowException $e ) { }
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=membersettings&controller=reputation&tab=levels' ) );
}
@@ -1,308 +0,0 @@
<?php
/**
* @brief menu
* @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 12 Jun 2019
*/
namespace IPS\core\modules\admin\mobile;
/* 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;
}
/**
* menu
*/
class _menu extends \IPS\Dispatcher\Controller
{
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'menu_manage' );
\IPS\Output::i()->responsive = FALSE;
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'system/menumanager.css', 'core', 'admin' ) );
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'admin_system.js', 'core', 'admin' ) );
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'jquery/jquery.nestedSortable.js', 'core', 'interface' ) );
parent::execute();
}
/**
* Manage Menu
*
* @return void
*/
protected function manage()
{
/* Get items from the database */
$items = array();
foreach ( \IPS\Db::i()->select( '*', 'core_mobileapp_menu', NULL, 'position' ) as $item )
{
if ( \IPS\Application::appIsEnabled( $item['app'] ) )
{
$class = 'IPS\\' . $item['app'] . '\extensions\core\MobileNavigation\\' . $item['extension'];
if ( class_exists( $class ) )
{
$items[ $item['id'] ] = new $class( json_decode( $item['config'], TRUE ), $item['id'], $item['permissions'] );
}
}
}
/* Display */
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_mobile_menu');
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'applications' )->menuManager( array( 0 => $items ), array(), \IPS\Http\Url::internal('app=core&module=mobile&controller=menu'), FALSE, FALSE );
}
/**
* Add Menu Item
*
* @return void
*/
protected function form()
{
/* What menu items have we already configured? */
$current = array();
foreach ( \IPS\Db::i()->select( '*', 'core_mobileapp_menu', NULL, 'position' ) as $item )
{
if ( !isset( $current[ $item['app'] ][ $item['extension'] ] ) )
{
$current[ $item['app'] ][ $item['extension'] ] = 0;
}
$current[ $item['app'] ][ $item['extension'] ]++;
}
/* Are we editing an existing item? */
$existing = NULL;
if ( \IPS\Request::i()->id )
{
try
{
$existing = \IPS\Db::i()->select( '*', 'core_mobileapp_menu', array( 'id=?', \IPS\Request::i()->id ) )->first();
}
catch ( \OutOfRangeException $e ) { }
}
/* What options are available? */
$options = array();
$disabled = array();
$toggles = array();
$fieldNames = array();
$extraFields = array();
$haveInheritable = FALSE;
foreach ( \IPS\Application::allExtensions( 'core', 'MobileNavigation', FALSE, 'core', NULL, FALSE ) as $key => $class )
{
if ( method_exists( $class, 'typeTitle' ) )
{
$exploded = explode( '_', $key );
if ( $class::allowMultiple() or !isset( $current[ $exploded[0] ][ $exploded[1] ] ) or ( $existing and $existing['app'] == $exploded[0] and $existing['extension'] == $exploded[1] ) )
{
$options[ $key ] = $class::typeTitle();
foreach ( $class::configuration( $existing ? json_decode( $existing['config'], TRUE ) : array(), $existing ? $existing['id'] : NULL ) as $field )
{
if ( !$field->htmlId )
{
$field->htmlId = md5( mt_rand() );
}
$toggles[ $key ][ $field->name ] = $field->htmlId;
$fieldNames[ $key ][] = $field->name;
$extraFields[] = $field;
}
if ( $class::permissionsCanInherit() )
{
$haveInheritable = TRUE;
$toggles[ $key ][] = 'menu_manager_access_type';
}
else
{
$toggles[ $key ][] = 'menu_manager_access';
}
}
if ( !$class::isEnabled() )
{
$disabled[] = $key;
}
}
}
/* Create the form */
$form = new \IPS\Helpers\Form( 'menu_item', 'save_menu_item' );
$form->class = 'ipsForm_vertical';
$form->add( new \IPS\Helpers\Form\Radio( 'menu_manager_extension', $existing ? "{$existing['app']}_{$existing['extension']}" : NULL, TRUE, array( 'options' => $options, 'toggles' => $toggles, 'disabled' => $disabled ) ) );
foreach ( $extraFields as $field )
{
$form->add( $field );
}
$groups = array();
foreach ( \IPS\Member\Group::groups() as $group )
{
$groups[ $group->g_id ] = $group->name;
}
if ( $haveInheritable )
{
$form->add( new \IPS\Helpers\Form\Radio( 'menu_manager_access_type', ( $existing and $existing['permissions'] !== NULL ) ? 1 : 0, TRUE, array(
'options' => array( 0 => 'menu_manager_access_type_inherit', 1 => 'menu_manager_access_type_override' ),
'toggles' => array( 1 => array( 'menu_manager_access' ) )
), NULL, NULL, NULL, 'menu_manager_access_type' ) );
}
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'menu_manager_access', $existing ? ( $existing['permissions'] == '*' ? '*' : explode( ',', $existing['permissions'] ) ) : '*', NULL, array( 'multiple' => TRUE, 'options' => $groups, 'unlimited' => '*', 'unlimitedLang' => 'everyone', 'impliedUnlimited' => TRUE ), NULL, NULL, NULL, 'menu_manager_access' ) );
if( !$existing ){
$form->hiddenValues['newItem'] = TRUE;
}
/* Handle submissions */
if ( $values = $form->values() )
{
$exploded = explode( '_', $values['menu_manager_extension'] );
$class = 'IPS\\' . $exploded[0] . '\extensions\core\MobileNavigation\\' . $exploded[1];
$config = array();
if ( isset( $fieldNames[ $values['menu_manager_extension'] ] ) )
{
foreach ( $values as $k => $v )
{
if ( \in_array( $k, $fieldNames[ $values['menu_manager_extension'] ] ) )
{
$config[ $k ] = $v;
}
}
}
$save = array(
'app' => $exploded[0],
'extension' => $exploded[1],
'config' => json_encode( $config ),
);
/* First we need to determine if the access type option was even shown */
$hasAccessType = ( \in_array( 'menu_manager_access_type', $toggles[ $values['menu_manager_extension'] ] ) ) ? TRUE : FALSE;
/* If we didn't have the access type field, then we should just check the permissions that were specified */
if ( !$hasAccessType or $values['menu_manager_access_type'] )
{
$save['permissions'] = $values['menu_manager_access'] == '*' ? '*' : implode( ',', $values['menu_manager_access'] );
}
else
{
$save['permissions'] = $class::permissionsCanInherit() ? NULL : '';
}
if ( $existing )
{
$id = $existing['id'];
$_config = $class::parseConfiguration( $config, $id );
if ( $_config != $config )
{
$save['config'] = json_encode( $_config );
}
\IPS\Db::i()->update( 'core_mobileapp_menu', $save, array( 'id=?', $id ) );
}
else
{
try
{
$save['position'] = \IPS\Db::i()->select( 'MAX(position)', 'core_mobileapp_menu' )->first() + 1;
}
catch ( \UnderflowException $e )
{
$save['position'] = 1;
}
$id = \IPS\Db::i()->insert( 'core_mobileapp_menu', $save );
$_config = $class::parseConfiguration( $config, $id );
if ( $_config != $config )
{
\IPS\Db::i()->update( 'core_mobileapp_menu', array( 'config' => json_encode( $_config ) ), array( 'id=?', $id ) );
}
}
if( \IPS\Request::i()->isAjax() )
{
$item = new $class( $_config, $id, $save['permissions'] );
$output = array(
'menu_item' => \IPS\Theme::i()->getTemplate( 'applications' )->menuItem( $item, $id, \IPS\Http\Url::internal('app=core&module=mobile&controller=menu') ),
'id' => $id
);
\IPS\Output::i()->json( $output );
}
else
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal('app=core&module=mobile&controller=menu') );
}
}
/* Display */
\IPS\Output::i()->output = $form->customTemplate( array( \IPS\Theme::i()->getTemplate( 'applications', 'core' ), 'menuManagerForm' ) );
if ( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->sendOutput( \IPS\Output::i()->output );
}
}
/**
* Remove an item
*
* @return void
*/
protected function remove()
{
\IPS\Session::i()->csrfCheck();
static::_remove( \intval( \IPS\Request::i()->id ) );
\IPS\Output::i()->json('OK');
}
/**
* Remove a menu item
*
* @param int $id ID of item to remove
* @return void
*/
protected static function _remove( $id )
{
\IPS\Db::i()->delete( 'core_mobileapp_menu', array( 'id=?', $id ) );
/* remove the title language strings */
\IPS\Lang::deleteCustom( 'core', "mobilemenu_item_{$id}" );
}
/**
* Reorder Items
*
* @return void
*/
protected function reorder()
{
\IPS\Session::i()->csrfCheck();
$position = 1;
foreach ( \IPS\Request::i()->menu_order as $id )
{
\IPS\Db::i()->update( 'core_mobileapp_menu', array(
'position' => $position++,
), array( 'id=?', $id ) );
}
\IPS\Output::i()->json('OK');
}
}
@@ -1,658 +0,0 @@
<?php
/**
* @brief Mobile App Settings
* @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 18 Mar 2019
*/
namespace IPS\core\modules\admin\mobile;
/* 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;
}
/**
* Mobile App Settings
*/
class _mobile extends \IPS\Dispatcher\Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* Execute
*
* @return void
*/
public function execute()
{
if ( \IPS\DEMO_MODE )
{
\IPS\Output::i()->error( 'demo_mode_function_blocked', '1C418/1', 403, '' );
}
\IPS\Dispatcher::i()->checkAcpPermission( 'mobile_manage' );
parent::execute();
}
/**
* Mobile App Page
*
* @return void
*/
protected function manage()
{
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'menu__core_mobile' );
/* If the mobile app has been set up, show the details page... */
if ( \IPS\Settings::i()->mobile_app_id )
{
try
{
/* Get details from directory */
$details = \IPS\Http\Url::ips('mobile/directory')->setQueryString( array( 'id' => \IPS\Settings::i()->mobile_app_id ) )->request()->login( \IPS\Settings::i()->mobile_app_id, \IPS\Settings::i()->mobile_app_secret )->get()->decodeJson();
if ( \count( $details ) !== 1 )
{
if ( \count( $details ) === 0 )
{
\IPS\Settings::i()->changeValues( array( 'mobile_app_id' => '', 'mobile_app_secret' => '' ) );
return $this->_notSetup();
}
else
{
throw new \DomainException;
}
}
$details = $details[0];
$langaugeOptions = \IPS\Http\Url::ips('mobile/languages')->request()->get()->decodeJson();
$languages = [];
foreach ( $details['languages'] as $langCode )
{
if ( array_key_exists( $langCode, $langaugeOptions ) )
{
$langauges[] = $langaugeOptions[ $langCode ];
}
}
$details['languages'] = $langauges;
/* If it says the status is other than what we expect, refresh our license key cache */
if ( $licenseKey = \IPS\IPS::licenseKey() and ( ( $details['status'] === 'active' and !$licenseKey['active'] ) or ( $details['status'] === 'expired' and !$licenseKey['active'] ) ) )
{
$licenseKey = \IPS\IPS::licenseKey( TRUE );
}
/* Unless we're suspended, show a button to turn off mobile app integration */
if ( $details['category'] === 'unlisted' or \in_array( $details['status'], array( 'pending', 'active', 'expired' ) ) )
{
\IPS\Output::i()->sidebar['actions']['deregister'] = array(
'icon' => 'times',
'title' => 'disable',
'link' => \IPS\Http\Url::internal('app=core&module=mobile&controller=mobile&do=deregister')->csrf(),
'data' => array(
'confirm' => '',
'confirmSubMessage' => $details['category'] === 'unlisted' ? '' : \IPS\Member::loggedIn()->language()->addToStack('mobile_app_deregister_confirm')
)
);
}
/* Build the settings form */
$settings = NULL;
if ( \in_array( $details['status'], array( 'active' ) ) )
{
\IPS\Output::i()->sidebar['actions']['tokens'] = array(
'icon' => 'key',
'title' => 'oauth_view_authorizations',
'link' => \IPS\Http\Url::internal('app=core&module=applications&controller=api&tab=oauth')->setQueryString( array( 'do' => 'tokens', 'client_id' => $this->_getClient()->client_id ) )
);
$settings = new \IPS\Helpers\Form;
$settings->add( new \IPS\Helpers\Form\Sort( 'mobile_app_home_blocks_order', json_decode( \IPS\Settings::i()->mobile_app_home_blocks_order, TRUE ), FALSE, array( 'checkboxes' => 'mobile_app_home_blocks_' ) ) );
if ( $details['category'] === 'unlisted' )
{
$settings->add( new \IPS\Helpers\Form\Text( 'mobile_app_redirect_scheme', \IPS\Settings::i()->mobile_app_redirect_scheme, FALSE, array( 'placeholder' => 'your-app-schema' ), NULL, NULL, '://', 'mobile_app_redirect_scheme' ) );
}
$settings->add( new \IPS\Helpers\Form\YesNo( 'mobile_app_prompt', \IPS\Settings::i()->mobile_app_prompt, FALSE, array( 'togglesOn' => array( 'mobile_app_itunes_id', 'mobile_app_itunes_url', 'mobile_app_play_id', 'mobile_app_play_url', 'itunes_affiliate_string' ) ) ) );
if( $details['category'] !== 'unlisted' )
{
$settings->add( new \IPS\Helpers\Form\YesNo( 'mobile_app_hide_stats', \IPS\Settings::i()->mobile_app_hide_stats, FALSE ) );
}
if ( $details['category'] === 'unlisted' )
{
$settings->add( new \IPS\Helpers\Form\Text( 'mobile_app_itunes_id', \IPS\Settings::i()->mobile_app_itunes_id, FALSE, array( 'placeholder' => '123456789' ), NULL, NULL, NULL, 'mobile_app_itunes_id' ) );
$settings->add( new \IPS\Helpers\Form\Url( 'mobile_app_itunes_url', \IPS\Settings::i()->mobile_app_itunes_url, FALSE, array( 'placeholder' => 'https://itunes.apple.com/us/app/my-app/123456789' ), NULL, NULL, NULL, 'mobile_app_itunes_url' ) );
$settings->add( new \IPS\Helpers\Form\Text( 'mobile_app_play_id', \IPS\Settings::i()->mobile_app_play_id, FALSE, array( 'placeholder' => 'com.company.appname' ), NULL, NULL, NULL, 'mobile_app_play_id' ) );
$settings->add( new \IPS\Helpers\Form\Url( 'mobile_app_play_url', \IPS\Settings::i()->mobile_app_play_url, FALSE, array( 'placeholder' => 'https://play.google.com/store/apps/details?id=com.company.appname' ), NULL, NULL, NULL, 'mobile_app_play_url' ) );
}
$settings->add( new \IPS\Helpers\Form\Text( 'itunes_affiliate_string', \IPS\Settings::i()->itunes_affiliate_string, FALSE, array(), NULL, NULL, NULL, 'itunes_affiliate_string' ) );
if ( $values = $settings->values() )
{
$values['mobile_app_home_blocks_order'] = json_encode( $values['mobile_app_home_blocks_order'] );
if ( $details['category'] !== 'unlisted' )
{
$values['mobile_app_redirect_scheme'] = '';
$values['mobile_app_itunes_id'] = '';
$values['mobile_app_itunes_url'] = '';
$values['mobile_app_play_id'] = '';
$values['mobile_app_play_url'] = '';
}
$settings->saveAsSettings( $values );
if ( $details['category'] == 'unlisted' and $values['mobile_app_redirect_scheme'] )
{
$this->_updateClient( $this->_getClient() );
}
unset( \IPS\Data\Store::i()->manifest );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal('app=core&module=mobile&controller=mobile') );
}
}
elseif ( \IPS\Settings::i()->mobile_app_prompt )
{
\IPS\Settings::i()->changeValues( array( 'mobile_app_prompt' => 0 ) );
unset( \IPS\Data\Store::i()->manifest );
}
/* Display */
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'mobile' )->details( $details, $settings );
}
catch ( \Exception $e )
{
\IPS\Output::i()->error( 'mobile_app_registration_error', '4C397/7', 500, NULL, array(), \IPS\IPS::getExceptionDetails( $e ) );
}
}
/* Otherwise show the splash screen */
else
{
return $this->_notSetup();
}
}
/**
* Mobile App: Not Setup
*
* @return void
*/
protected function _notSetup()
{
\IPS\Output::i()->sidebar['actions']['manual'] = array(
'icon' => 'cog',
'title' => 'mobile_app_splash_manual',
'link' => \IPS\Http\Url::internal('app=core&module=mobile&controller=mobile&do=manual'),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('mobile_app_splash_manual') )
);
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'mobile' )->splash();
}
/**
* Mobile App: Submit to Multi Community
*
* @return void
*/
protected function submit()
{
/* Before we do anything, check the license key */
if ( $licenseKey = \IPS\IPS::licenseKey( TRUE ) )
{
if ( !$licenseKey['active'] )
{
\IPS\Output::i()->error( 'mobile_app_requires_active_license', '1C397/8', 403 );
}
}
else
{
\IPS\Output::i()->error( 'mobile_app_requires_license', '1C397/9', 403 );
}
/* Create the form */
$form = new \IPS\Helpers\Form( 'mobile_app_submit', 'submit' );
$existing = NULL;
if ( \IPS\Settings::i()->mobile_app_id )
{
try
{
$details = \IPS\Http\Url::ips('mobile/directory')->setQueryString( array( 'id' => \IPS\Settings::i()->mobile_app_id ) )->request()->login( \IPS\Settings::i()->mobile_app_id, \IPS\Settings::i()->mobile_app_secret )->get()->decodeJson();
if ( \count( $details ) !== 1 )
{
throw new \DomainException;
}
$existing = $details[0];
}
catch ( \Exception $e )
{
\IPS\Output::i()->error( 'mobile_app_registration_error', '4C397/6', 500, NULL, array(), \IPS\IPS::getExceptionDetails( $e ) );
}
$form->addMessage('mobile_app_listing_update_info');
}
$form->add( new \IPS\Helpers\Form\Text( 'mobile_app_listing_name', $existing ? ( $existing['pending_name'] ?: $existing['name'] ) : \IPS\Settings::i()->board_name, TRUE ) );
$form->add( new \IPS\Helpers\Form\TextArea( 'mobile_app_listing_desc', $existing ? ( $existing['pending_description'] ?: $existing['description'] ) : NULL, TRUE, array( 'maxLength' => 125 ) ) );
if ( $existing )
{
$form->add( new \IPS\Helpers\Form\Radio( 'mobile_app_listing_logo_choose', 'keep', TRUE, array(
'options' => array(
'keep' => 'mobile_app_listing_logo_keep',
'change' => 'mobile_app_listing_logo_change',
),
'toggles' => array(
'change' => array( 'mobile_app_listing_logo_upload' )
)
) ) );
}
$homeScreen = json_decode( \IPS\Settings::i()->icons_homescreen, TRUE ) ?? array();
$form->add( new \IPS\Helpers\Form\Upload( 'mobile_app_listing_logo', $existing ? NULL : ( ( isset( $homeScreen['original'] ) ) ? \IPS\File::get( 'core_Icons', $homeScreen['original'] ) : NULL ), TRUE, array( 'image' => TRUE, 'storageExtension' => 'core_Icons' ), function ( $file ) {
if ( $file )
{
$image = \IPS\Image::create( $file->contents() );
if ( !$image->isSquare() )
{
throw new \DomainException('mobile_app_listing_logo_square');
}
if ( $image->width < 512 )
{
throw new \DomainException('mobile_app_listing_logo_dims');
}
}
}, NULL, NULL, 'mobile_app_listing_logo_upload' ) );
try
{
$categoryData = \IPS\Http\Url::ips('mobile/categories')->request()->get()->decodeJson();
$categories = array();
foreach( $categoryData as $_key => $_cat )
{
$categories[ $_key ] = $_cat['title'];
}
$form->add( new \IPS\Helpers\Form\Select( 'mobile_app_listing_category', $existing ? $existing['category'] : 'general', TRUE, array( 'options' => $categories ) ) );
$langaugeOptions = \IPS\Http\Url::ips('mobile/languages')->request()->get()->decodeJson();
if ( $existing )
{
$langauges = explode( ',', $existing['languages'] );
}
else
{
$langauges = [];
foreach ( \IPS\Lang::languages() as $lang )
{
$code = mb_substr( $lang->bcp47(), 0, 2 );
if ( array_key_exists( $code, $langaugeOptions ) )
{
$langauges[] = $code;
}
}
if ( !$langauges )
{
$langauges = ['en'];
}
}
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'mobile_app_listing_languages', $existing ? explode( ',', $existing['languages'] ): ['en'], TRUE, array( 'multiple' => TRUE, 'options' => $langaugeOptions ) ) );
}
catch ( \Exception $e )
{
\IPS\Output::i()->error( 'mobile_app_registration_error', '4C397/3', 500, NULL, array(), \IPS\IPS::getExceptionDetails( $e ) );
}
/* Handle submissions */
if ( $values = $form->values() )
{
/* If we haven't set application icons for our manifest, use the icon provided */
if ( !isset( $homeScreen['original'] ) )
{
\IPS\Settings::i()->changeValues( \IPS\core\modules\admin\customization\icons::processApplicationIcon( array( 'icons_homescreen' => $values['mobile_app_listing_logo'] ), $homeScreen ) );
}
try
{
/* Work out logo */
$logo = NULL;
$logoExt = NULL;
$logoMime = NULL;
if ( !$existing or $values['mobile_app_listing_logo_choose'] === 'change' )
{
$logo = $values['mobile_app_listing_logo']->contents();
$logoExt = mb_substr( $values['mobile_app_listing_logo']->originalFilename, mb_strrpos( $values['mobile_app_listing_logo']->originalFilename, '.' ) + 1 );
$logoMime = \IPS\File::getMimeType( $values['mobile_app_listing_logo']->originalFilename );
}
/* Submit to IPS */
$response = $this->_updateRegistration( array(
'name' => $values['mobile_app_listing_name'],
'description' => $values['mobile_app_listing_desc'],
'category' => $values['mobile_app_listing_category'],
'logo' => $logo,
'logo_type' => $logoExt,
'logo_mime' => $logoMime,
'languages' => implode( ',', $values['mobile_app_listing_languages'] )
), $existing ? \IPS\Settings::i()->mobile_app_id : NULL, $existing ? \IPS\Settings::i()->mobile_app_secret : NULL );
/* If we already had an icon for our manifest, delete the logo we just uploaded */
if ( isset( $homeScreen['original'] ) and $values['mobile_app_listing_logo'] and (string) $homeScreen['original'] != (string) $values['mobile_app_listing_logo'] )
{
$values['mobile_app_listing_logo']->delete();
}
}
catch ( \DomainException $e )
{
\IPS\Output::i()->error( $e->getMessage(), '2C397/4', 500 );
}
catch ( \Exception $e )
{
\IPS\Output::i()->error( 'mobile_app_registration_error', '4C397/5', 500, NULL, array(), \IPS\IPS::getExceptionDetails( $e ) );
}
if ( $existing )
{
\IPS\Session::i()->log( 'acplogs__mobile_app_updated' );
}
else
{
$this->_doEnable( $response['id'], $response['secret'] );
\IPS\Session::i()->log( 'acplogs__mobile_app_registered' );
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal('app=core&module=mobile&controller=mobile') );
}
/* Display */
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'menu__core_mobile_mobile' );
\IPS\Output::i()->output = $form;
}
/**
* Mobile App: Activate Manually
*
* @return void
*/
protected function manual()
{
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'mobile_app_splash_manual' );
$form = new \IPS\Helpers\Form;
$form->addDummy( 'oauth_client_id', $this->_getClient( TRUE )->client_id );
$form->add( new \IPS\Helpers\Form\Text( 'mobile_app_splash_manual_id', NULL, TRUE, array(), function( $val ) {
try
{
$response = \IPS\Http\Url::ips('mobile/directory')->setQueryString( array( 'id' => $val ) )->request()->get()->decodeJson();
}
catch ( \Exception $e )
{
throw new \DomainException('mobile_app_registration_error');
}
if ( \count( $response ) !== 1 )
{
throw new \DomainException('mobile_app_splash_manual_error');
}
if ( $response[0]['url'] !== \IPS\Settings::i()->base_url )
{
throw new \DomainException('mobile_app_splash_manual_error_url');
}
} ) );
$form->add( new \IPS\Helpers\Form\Text( 'mobile_app_splash_manual_secret', NULL, TRUE, array(), function( $val ) {
try
{
$response = \IPS\Http\Url::ips('mobile/notify')->request()->login( \IPS\Request::i()->mobile_app_splash_manual_id, $val )->post( array( 'data' => array() ) );
}
catch ( \Exception $e )
{
throw new \DomainException('mobile_app_registration_error');
}
if ( $response->httpResponseCode != 200 )
{
throw new \DomainException('mobile_app_splash_manual_error');
}
} ) );
if ( $values = $form->values() )
{
try
{
$this->_updateRegistration( array( ), $values['mobile_app_splash_manual_id'], $values['mobile_app_splash_manual_secret'] );
}
catch ( \DomainException $e )
{
\IPS\Output::i()->error( $e->getMessage(), '2C397/1', 500 );
}
catch ( \Exception $e )
{
\IPS\Output::i()->error( 'mobile_app_registration_error', '4C397/2', 500, NULL, array(), \IPS\IPS::getExceptionDetails( $e ) );
}
$this->_doEnable( $values['mobile_app_splash_manual_id'], $values['mobile_app_splash_manual_secret'] );
\IPS\Session::i()->log( 'acplogs__mobile_app_activated' );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal('app=core&module=mobile&controller=mobile') );
}
\IPS\Output::i()->output = $form;
}
/**
* Mobile App: Deregister
*
* @return void
*/
protected function deregister()
{
\IPS\Session::i()->csrfCheck();
if ( \IPS\Settings::i()->mobile_app_id )
{
try
{
\IPS\Http\Url::ips('mobile/unregister')->request()->login( \IPS\Settings::i()->mobile_app_id, \IPS\Settings::i()->mobile_app_secret )->post( array(
'lkey' => trim( \IPS\Settings::i()->ipb_reg_number ),
) );
}
catch ( \Exception $e )
{
// It doesn't really matter if this failed (which could happen if the stores secret was somehow wrong)
// It might just require tech support to go in and manually remove them before they can register again
}
\IPS\Settings::i()->changeValues( array(
'mobile_app_id' => '',
'mobile_app_secret' => '',
) );
}
if ( $client = $this->_getClient() )
{
$client->delete();
}
\IPS\Session::i()->log( 'acplogs__mobile_app_unregistered' );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=mobile&controller=mobile' ) );
}
/**
* Get OAuth Client if there is one
*
* @bool bool $createIfNot If there isn't one, create one
* @return \IPS\Api\OAuthClient|NULL
*/
protected function _getClient( $createIfNot = FALSE )
{
try
{
return \IPS\Api\OAuthClient::constructFromData( \IPS\Db::i()->select( '*', 'core_oauth_clients', array( 'oauth_type=?', 'mobile' ) )->first() );
}
catch ( \UnderflowException $e )
{
if ( $createIfNot )
{
$client = $this->_createClient();
$this->_updateClient( $client );
return $client;
}
else
{
return NULL;
}
}
}
/**
* Create an OAuth Client
*
* @return \IPS\Api\OAuthClient
*/
protected function _createClient()
{
$client = new \IPS\Api\OAuthClient;
do
{
$client->client_id = \IPS\Login::generateRandomString( 32 );
}
while ( \IPS\Db::i()->select( 'COUNT(*)', 'core_oauth_clients', array( 'oauth_client_id=?', $client->client_id ) )->first() );
$client->enabled = TRUE;
$client->prompt = 'none';
$client->ucp = FALSE;
\IPS\Lang::copyCustom( 'core', 'mobile_app_default_client_name', "core_oauth_client_{$client->client_id}" );
return $client;
}
/**
* Update OAuth Client with appropriate settings
*
* @param \IPS\Api\OAuthClient $client The client
* @return void
*/
protected function _updateClient( \IPS\Api\OAuthClient $client )
{
$client->client_secret = NULL;
foreach ( \IPS\Api\OAuthClient::mobileAppValues() as $k => $v )
{
if ( $k === 'redirect_uris' or $k === 'scopes' )
{
$client->$k = json_encode( $v );
}
else
{
$client->$k = \is_array( $v ) ? implode( ',', $v ) : $v;
}
}
$client->save();
}
/**
* Update registration details on remoteservices
*
* @param array $details Details to update (name, description, etc)
* @param string|NULL $appId If updating, the ID
* @param string|NULL $appSecret If updating, the secret
* @return array
*/
protected function _updateRegistration( $details, $appId = NULL, $appSecret = NULL )
{
/* First of all, create or update our OAuth Client */
$client = $this->_getClient();
if ( !$client )
{
$client = $this->_createClient();
}
$this->_updateClient( $client );
/* Set details to send */
$details['lkey'] = trim( \IPS\Settings::i()->ipb_reg_number );
$details['url'] = \IPS\Settings::i()->base_url;
$details['client_id'] = $client->client_id;
/* Register or update remoteservices */
if ( $appId )
{
$response = \IPS\Http\Url::ips('mobile/register')->request()->login( $appId, $appSecret )->post( $details );
if ( $response->httpResponseCode != 200 )
{
throw new \DomainException( ( (string) $response ) ?: 'mobile_app_registration_error' );
}
}
else
{
$response = \IPS\Http\Url::ips('mobile/register')->request()->post( $details );
if ( $response->httpResponseCode != 201 )
{
throw new \DomainException( ( (string) $response ) ?: 'mobile_app_registration_error' );
}
}
return $response->decodeJson();
}
/**
* Enable mobile app
*
* @param string $appId If updating, the ID
* @param string $appSecret If updating, the secret
* @return array
*/
protected function _doEnable( $appId, $appSecret )
{
\IPS\Settings::i()->changeValues( array(
'mobile_app_id' => $appId,
'mobile_app_secret' => $appSecret,
) );
/* Add it to our notification defaults */
if ( \IPS\Db::i()->select( 'COUNT(*)', 'core_notification_defaults' )->first() )
{
$keysToAddPushTo = array();
foreach( \IPS\Application::allExtensions( 'core', 'Notifications', FALSE ) as $group => $class )
{
if ( method_exists( $class, 'configurationOptions' ) )
{
foreach ( $class->configurationOptions( NULL ) as $key => $data )
{
if ( $data['type'] == 'standard' and \in_array( 'push', $data['default'] ) )
{
$keysToAddPushTo[] = $key;
foreach ( $data['notificationTypes'] as $type )
{
$keysToAddPushTo[] = $type;
}
}
}
}
elseif ( method_exists( $class, 'getConfiguration' ) )
{
$configuration = $class->getConfiguration( NULL );
if ( !empty( $configuration ) )
{
foreach ( $configuration as $key => $data )
{
if ( \in_array( 'push', $data['default'] ) )
{
$keysToAddPushTo[] = $key;
}
}
}
}
}
if ( \count( $keysToAddPushTo ) )
{
\IPS\Db::i()->update( 'core_notification_defaults', "`default` = IF( `default` = '', 'push', CONCAT_WS( ',', `default`, 'push' ) )", \IPS\Db::i()->in( 'notification_key', $keysToAddPushTo ) );
}
}
}
}
@@ -116,22 +116,23 @@ class _spam extends \IPS\Dispatcher\Controller
switch( $type )
{
case 'options':
return array( 'none' => 'captcha_type_none', 'invisible' => 'captcha_type_invisible', 'recaptcha2' => 'captcha_type_recaptcha2', 'keycaptcha' => 'captcha_type_keycaptcha' );
break;
return array( 'none' => 'captcha_type_none', 'invisible' => 'captcha_type_invisible', 'recaptcha2' => 'captcha_type_recaptcha2', 'keycaptcha' => 'captcha_type_keycaptcha', 'hcaptcha' => 'captcha_type_hcaptcha' );
break;
case 'toggles':
return array(
'none' => array( 'bot_antispam_type_warning' ),
'recaptcha2' => array( 'guest_captcha', 'recaptcha2_public_key', 'recaptcha2_private_key' ),
'invisible' => array( 'guest_captcha', 'recaptcha2_public_key', 'recaptcha2_private_key' ),
'keycaptcha' => array( 'guest_captcha', 'keycaptcha_privatekey' )
'keycaptcha' => array( 'guest_captcha', 'keycaptcha_privatekey' ),
'hcaptcha' => array( 'guest_captcha', 'hcaptcha_sitekey', 'hcaptcha_secret')
);
break;
break;
}
return array();
}
/**
* Show CAPTCHA settings
*
@@ -148,6 +149,8 @@ class _spam extends \IPS\Dispatcher\Controller
$form->add( new \IPS\Helpers\Form\Text( 'recaptcha2_public_key', \IPS\Settings::i()->recaptcha2_public_key, FALSE, array(), NULL, NULL, NULL, 'recaptcha2_public_key' ) );
$form->add( new \IPS\Helpers\Form\Text( 'recaptcha2_private_key', \IPS\Settings::i()->recaptcha2_private_key, FALSE, array(), NULL, NULL, NULL, 'recaptcha2_private_key' ) );
$form->add( new \IPS\Helpers\Form\Text( 'keycaptcha_privatekey', \IPS\Settings::i()->keycaptcha_privatekey, FALSE, array(), NULL, NULL, NULL, 'keycaptcha_privatekey' ) );
$form->add( new \IPS\Helpers\Form\Text( 'hcaptcha_sitekey', \IPS\Settings::i()->hcaptcha_sitekey, FALSE, array(), NULL, NULL, NULL, 'hcaptcha_sitekey' ) );
$form->add( new \IPS\Helpers\Form\Text( 'hcaptcha_secret', \IPS\Settings::i()->hcaptcha_secret, FALSE, array(), NULL, NULL, NULL, 'hcaptcha_secret' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'guest_captcha', \IPS\Settings::i()->guest_captcha, FALSE, array(), NULL, NULL, NULL, 'guest_captcha' ) );
/* Save values */
@@ -217,8 +220,8 @@ class _spam extends \IPS\Dispatcher\Controller
}
}
$form->add( new \IPS\Helpers\Form\YesNo( 'spam_service_enabled', \IPS\Settings::i()->spam_service_enabled, 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_send_to_ips', \IPS\Settings::i()->spam_service_send_to_ips, FALSE, array( 'disabled' => $disabled ), NULL, NULL, NULL, 'spam_service_send_to_ips' ) );
$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_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' ) );
$form->add( new \IPS\Helpers\Form\Select( 'spam_service_action_2', \IPS\Settings::i()->spam_service_action_2, FALSE, array( 'disabled' => $disabled, 'options' => $actions, 'toggles' => array( '5' => array( 'spam_service_action_2_num' ) ) ), NULL, NULL, NULL, 'spam_service_action_2' ) );
@@ -210,5 +210,24 @@ class _dashboard extends \IPS\Dispatcher\Controller
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=overview&controller=dashboard" ), 'saved' );
}
}
/**
* Snooze the switch to invision link for a bit
*
* @return void
*/
public function switchSnooze()
{
$value = [ 'hits' => 1, 'lastClick' => time() ];
if ( isset( \IPS\Request::i()->cookie['acpLinkSnooze'] ) and $value = json_decode( \IPS\Request::i()->cookie['acpLinkSnooze'], true ) )
{
$value['hits'] = \intval( $value['hits'] ) + 1;
$value['lastClick'] = time();
}
\IPS\Request::i()->setCookie( 'acpLinkSnooze', json_encode( $value ), ( new \IPS\DateTime )->add( new \DateInterval( 'P2Y' ) ) );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=overview&controller=dashboard" ), 'The Switch to Cloud link won\'t show for a while' );
}
}
@@ -219,7 +219,7 @@ class _files extends \IPS\Dispatcher\Controller
}
}
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->block( NULL, \IPS\Theme::i()->getTemplate( 'members', 'core', 'global' )->attachmentLocations( $locations, FALSE ) );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->block( NULL, \IPS\Theme::i()->getTemplate( 'members', 'core', 'global' )->attachmentLocations( $locations, FALSE ), TRUE, 'ipsPad' );
}
/**
@@ -381,7 +381,7 @@ class _files extends \IPS\Dispatcher\Controller
{
if ( isset( $settings[ $k ] ) AND !\is_array( $settings[ $k ] ) )
{
if( $settings[ $k ] != $v AND $k != 'filestorage_move' AND \in_array( $v, $cicDisabled ) )
if( $settings[ $k ] != $v AND $k != 'filestorage_move' AND \IPS\CIC AND \in_array( $v, $cicDisabled ) )
{
\IPS\Output::i()->error( 'file_storage_cic_filesystem', '3C158/7', 403, '' );
}
@@ -862,7 +862,7 @@ class _files extends \IPS\Dispatcher\Controller
protected function configurationLog()
{
$method = \IPS\Db::i()->select( '*', 'core_file_storage', array( 'id=?', \intval( \IPS\Request::i()->id ) ) )->first();
$title = \IPS\Member::loggedIn()->language()->addToStack( 'file_config_log', NULL, array( 'sprintf' => array( $method['method'] ) ) );
$title = \IPS\Member::loggedIn()->language()->addToStack( 'file_config_log', FALSE, array( 'sprintf' => array( $method['method'] ) ) );
/* Create the table */
$table = new \IPS\Helpers\Table\Db( 'core_file_logs', \IPS\Http\Url::internal( 'app=core&module=overview&controller=files&do=configurationLog&id=' . \IPS\Request::i()->id ), array( array( 'log_configuration_id=?', \IPS\Request::i()->id ) ) );
@@ -1,110 +0,0 @@
<?php
/**
* @brief Analytics
* @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 03 Nov 2016
*/
namespace IPS\core\modules\admin\promotion;
/* 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;
}
/**
* Analytics
*/
class _analytics extends \IPS\Dispatcher\Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'analytics_manage' );
parent::execute();
}
/**
* Analytics Settings
*
* @return void
*/
protected function manage()
{
$providers = array(
'none' => 'analytics_provider_none',
'ga' => 'analytics_provider_ga',
'piwik' => 'analytics_provider_piwik',
'custom' => 'analytics_provider_custom'
);
$toggles = array(
'ga' => array('analytics_ga'),
'piwik' => array('analytics_piwik'),
'custom' => array('analytics_custom', 'ipbseo_ga_paginatecode')
);
$form = new \IPS\Helpers\Form;
$form->addMessage('analytics_description');
$form->add( new \IPS\Helpers\Form\Radio( 'ipbseo_ga_provider', \IPS\Settings::i()->ipbseo_ga_provider, FALSE, array( 'options' => $providers, 'toggles' => $toggles ) ) );
/* Add a CodeMirror element for each supported provider */
foreach( $providers as $key => $provider )
{
if( $key == 'none' )
{
continue;
}
$form->add( new \IPS\Helpers\Form\Codemirror( 'analytics_' . $key, \IPS\Settings::i()->ipbseo_ga_provider == $key ? \IPS\Settings::i()->ipseo_ga : '', FALSE, array('height' => 150, 'mode' => 'javascript'), NULL, NULL, NULL, 'analytics_' . $key ) );
}
$form->add( new \IPS\Helpers\Form\Codemirror( 'ipbseo_ga_paginatecode', \IPS\Settings::i()->ipbseo_ga_paginatecode, FALSE, array('height' => 150, 'mode' => 'javascript'), NULL, NULL, NULL, 'ipbseo_ga_paginatecode' ) );
if ( $values = $form->values() )
{
/* Fix up values to ensure the correct snippet is saved */
if ( !isset( $providers[ $values['ipbseo_ga_provider'] ] ) || $values['ipbseo_ga_provider'] == 'none' )
{
$values['ipbseo_ga_enabled'] = FALSE;
$values['ipseo_ga'] = '';
$values['ipbseo_ga_paginatecode'] = '';
}
else
{
$values['ipbseo_ga_enabled'] = TRUE;
$values['ipseo_ga'] = $values['analytics_' . $values['ipbseo_ga_provider'] ];
}
foreach( $providers as $key => $provider )
{
unset( $values['analytics_' . $key ] );
}
$form->saveAsSettings( $values );
\IPS\Session::i()->log( 'acplog__analytics_edited' );
\IPS\Output::i()->inlineMessage = \IPS\Member::loggedIn()->language()->addToStack('saved');
/* Clear guest page caches */
\IPS\Data\Cache::i()->clearAll();
}
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_promotion_analytics');
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->block( 'analytics', $form );
}
}
@@ -91,7 +91,7 @@ class _promote extends \IPS\Dispatcher\Controller
$output = \IPS\Theme::i()->getTemplate( 'forms', 'core' )->blurb( \IPS\Member::loggedIn()->language()->addToStack( 'promote_acp_blurb' ) );
/* Put up a little warning about permissions */
$enabledServices = \IPS\Db::i()->select( 'count(*)', 'core_social_promote_sharers', array( 'sharer_enabled=1' ) );
$enabledServices = \IPS\Db::i()->select( 'count(*)', 'core_social_promote_sharers', array( 'sharer_enabled=1' ) )->first();
if ( $enabledServices and ! \IPS\core\Promote::canPromote() )
{
@@ -464,13 +464,13 @@ class _promote extends \IPS\Dispatcher\Controller
foreach( $pages as $id => $data )
{
$options[ 'p_' . $id ] = \IPS\Member::loggedIn()->language()->addToStack( 'promote_facebook_page_prefix', NULL, array( 'sprintf' => array( $data[0] ) ) );
$options[ 'p_' . $id ] = \IPS\Member::loggedIn()->language()->addToStack( 'promote_facebook_page_prefix', FALSE, array( 'sprintf' => array( $data[0] ) ) );
}
$groups = $facebook->getGroups( \IPS\Member::loggedIn() );
foreach( $groups as $id => $data )
{
$options[ 'g_' . $id ] = \IPS\Member::loggedIn()->language()->addToStack( 'promote_facebook_group_prefix', NULL, array( 'sprintf' => array( $data[0] ) ) );
$options[ 'g_' . $id ] = \IPS\Member::loggedIn()->language()->addToStack( 'promote_facebook_group_prefix', FALSE, array( 'sprintf' => array( $data[0] ) ) );
}
$output = '';
@@ -85,6 +85,10 @@ class _seo extends \IPS\Dispatcher\Controller
{
$tabs['metatags'] = 'seo_tab_metatags';
}
if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'promotion', 'seo_furls' ) )
{
$tabs['robotstxt'] = 'seo_tab_robotstxt';
}
/* Display */
if ( $activeTabContents )
@@ -126,6 +130,103 @@ class _seo extends \IPS\Dispatcher\Controller
}
}
/**
* Manage robots.txt
*
* @return string
*/
protected function _manageRobotstxt()
{
$value = \IPS\Settings::i()->robots_txt;
if ( ! \in_array( \IPS\Settings::i()->robots_txt, ['off', 'default'] ) )
{
$value = 'custom';
}
$form = new \IPS\Helpers\Form;
if ( isset( \IPS\Request::i()->_saved ) )
{
/* Do we have an existing robots.txt file in the community directory? */
$dir = trim( str_replace( \IPS\CP_DIRECTORY . '/index.php', '', $_SERVER['PHP_SELF'] ), '/' );
/* Oh, the community is in a directory, so we need the user to download the file and manually upload it to root */
if ( $dir )
{
$rootUrl = trim( str_replace( $dir, '', (string) \IPS\Http\Url::baseUrl() ), '/' );
if ( \IPS\Settings::i()->robots_txt === 'off' )
{
$form->addMessage( \IPS\Member::loggedIn()->language()->addToStack( 'use_robotstxt_warning_off', FALSE, ['sprintf' => [$rootUrl]] ), 'ipsMessage ipsMessage_warning' );
}
else
{
$form->addMessage( \IPS\Member::loggedIn()->language()->addToStack( 'use_robotstxt_warning_download', FALSE, ['sprintf' => [$rootUrl]] ), 'ipsMessage ipsMessage_warning' );
}
}
else if ( ! \IPS\CIC2 AND file_exists( \IPS\ROOT_PATH . '/robots.txt' ) )
{
if ( \IPS\Settings::i()->robots_txt === 'off' )
{
$form->addMessage( \IPS\Member::loggedIn()->language()->addToStack( 'use_robotstxt_warning_existing_off', FALSE, ['sprintf' => [ rtrim( \IPS\Http\Url::baseUrl(), '/' ) ] ] ), 'ipsMessage ipsMessage_warning' );
}
else
{
$form->addMessage( \IPS\Member::loggedIn()->language()->addToStack( 'use_robotstxt_warning_existing_download', FALSE, ['sprintf' => [ rtrim( \IPS\Http\Url::baseUrl(), '/' ) ] ] ), 'ipsMessage ipsMessage_warning' );
}
}
}
$form->add( new \IPS\Helpers\Form\Radio( 'use_robotstxt', $value, FALSE, [
'options' => [
'off' => 'use_robotstxt_off',
'default' => 'use_robotstxt_default',
'custom' => 'use_robotstxt_custom'
],
'toggles' => [
'custom' => [ 'use_robotstxt_custom_editor' ]
]
], NULL, NULL, NULL, 'use_robotstxt' ) );
$form->add( new \IPS\Helpers\Form\Codemirror( 'use_robotstxt_custom_editor', ( $value === 'custom' ? \IPS\Settings::i()->robots_txt : '' ), FALSE, [], NULL, NULL, NULL, 'use_robotstxt_custom_editor' ) );
/* Are we saving? */
if ( $values = $form->values() )
{
$value = $values['use_robotstxt'];
if ( $values['use_robotstxt'] === 'custom' )
{
$value = $values['use_robotstxt_custom_editor'];
}
$form->saveAsSettings( [ 'robots_txt' => $value ] );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=promotion&controller=seo&tab=robotstxt&_saved=1" ) );
}
return $form;
}
/**
* Downloads the robots.txt file
*
* @throws \Exception
*/
protected function downloadRobotstxt()
{
if ( \IPS\Settings::i()->robots_txt != 'off' )
{
if ( \IPS\Settings::i()->robots_txt == 'default' )
{
\IPS\Output::i()->sendOutput( \IPS\Dispatcher\Front::robotsTxtRules(), 200, 'application/x-robotstxt', array( 'Content-Disposition' => 'attachment; filename=robots.txt' ) );
}
else
{
\IPS\Output::i()->sendOutput( \IPS\Settings::i()->robots_txt, 200, 'application/x-robotstxt', array( 'Content-Disposition' => 'attachment; filename=robots.txt' ) );
}
}
}
/**
* Manage FURL definitions
*
@@ -526,7 +627,7 @@ Options -MultiViews
RewriteEngine On
RewriteBase {$dir}
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule \\.(js|css|jpeg|jpg|gif|png|ico|map)(\\?|$) {$dir}404error.php [L,NC]
RewriteRule \\.(js|css|jpeg|jpg|gif|png|ico|map|webp)(\\?|$) {$dir}404error.php [L,NC]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
@@ -76,6 +76,7 @@ class _advanced extends \IPS\Dispatcher\Controller
{
$tabs['datastore'] = 'data_store';
}
$tabs['pageoutput'] = 'page_output';
/* Display */
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_settings_advanced');
@@ -546,6 +547,28 @@ class _advanced extends \IPS\Dispatcher\Controller
return $form;
}
/**
* Settings
*
* @return string
*/
protected function _managePageoutput()
{
/* Build and show form */
$form = new \IPS\Helpers\Form;
$form->add( new \IPS\Helpers\Form\Codemirror( 'custom_body_code', \IPS\Settings::i()->custom_body_code, FALSE, array('height' => 150, 'mode' => 'javascript'), NULL, NULL, NULL, 'custom_body_code' ) );
$form->add( new \IPS\Helpers\Form\Codemirror( 'custom_page_view_js', \IPS\Settings::i()->custom_page_view_js, FALSE, array('height' => 150, 'mode' => 'javascript'), NULL, NULL, NULL, 'custom_page_view_js' ) );
if ( $values = $form->values() )
{
$form->saveAsSettings( $values );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=settings&controller=advanced&tab=pageoutput' ), 'saved' );
}
return $form;
}
/**
* Tasks
*
@@ -570,6 +593,8 @@ class _advanced extends \IPS\Dispatcher\Controller
$table->sortBy = $table->sortBy ?: 'next_run';
$table->sortDirection = $table->sortDirection ?: 'asc';
$table->noSort = array( 'frequency' );
$table->quickSearch = function( $val )
{
$matches = \IPS\Member::loggedIn()->language()->searchCustom( 'task__', $val, TRUE );
@@ -0,0 +1,769 @@
<?php
/**
* @brief dataLayer
* @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 08 Feb 2022
*/
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;
}
/**
* dataLayer
*/
class _dataLayer extends \IPS\Dispatcher\Controller
{
/**
* @breif the tabs
*/
protected static $tabs = array(
'main' => 'datalayer_main',
'pageContext' => 'datalayer_pageContext',
'properties' => 'datalayer_properties',
'events' => 'datalayer_events',
);
/**
* @breif Makes this controller work
*/
public static $csrfProtected = true;
/**
* @brief used to actually handle events and properties in the browser
*/
public static $handlerClass = '\IPS\core\DataLayer\Handler';
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'dataLayer_manage' );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'menu__core_settings_dataLayer' );
/* Can we use custom handlers? Only show the tab on sites that have full analytics */
$class = static::$handlerClass;
if (
class_exists( $class ) AND
class_exists( 'IPS\cloud\Application' ) AND
method_exists( $class, 'handlerForm' ) AND
method_exists( $class, 'loadWhere' ) AND
\IPS\Member::loggedIn()->hasAcpRestriction( 'dataLayer_handlers_view' ) AND
\IPS\cloud\Application::featureIsEnabled('analytics_full')
)
{
static::$tabs = array(
'main' => 'datalayer_main',
'pageContext' => 'datalayer_pageContext',
'handlers' => 'datalayer_handlers',
'properties' => 'datalayer_properties',
'events' => 'datalayer_events',
);
}
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'settings/general.css', 'core', 'admin' ) );
parent::execute();
}
/**
* Call Magic Method, seamless way to integrate cloud features if there are any
*
* @return string|void
*/
public function __call( $name, $arguments )
{
if ( method_exists( $this, $name ) )
{
return \call_user_func_array( $this->$name, $arguments );
}
elseif ( method_exists( \IPS\core\DataLayer::i(), $name ) )
{
$method = array( \IPS\core\DataLayer::i(), $name );
return \call_user_func_array( $method, $arguments );
}
return "You should Upgrade!";
}
/**
* ...
*
* @return void
*/
protected function manage()
{
$tab = \IPS\Request::i()->tab;
$tab = empty( static::$tabs[ $tab ] ) ? 'main' : $tab;
$output = $this->$tab();
$output = "<div id='dataLayerContent'>$output</div>" ;
if ( isset( static::$tabs['handlers'] ) AND \IPS\Member::loggedIn()->hasAcpRestriction( 'dataLayer_handlers_edit' ) )
{
\IPS\Output::i()->sidebar['actions']['addHandler'] = array(
'title' => \IPS\Member::loggedIn()->language()->addToStack( 'datalayer_handler_form' ),
'icon' => 'plus',
'link' => \IPS\Http\Url::internal( 'app=core&module=settings&controller=dataLayer&do=addHandler' ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack( 'datalayer_handler_form' ) )
);
}
$addPropertyUrl = \IPS\Http\Url::internal( 'app=core&module=settings&controller=dataLayer&do=addProperty' );
\IPS\Output::i()->sidebar['actions']['addProperty'] = array(
'title' => \IPS\Member::loggedIn()->language()->addToStack( 'datalayer_add_property' ),
'icon' => 'plus',
'link' => $addPropertyUrl,
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack( 'datalayer_add_property' ) )
);
if ( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->output = $output;
}
else
{
if ( isset( $_SESSION['deleted_datalayer_property'] ) )
{
\IPS\Output::i()->inlineMessage = 'Deleted Property';
unset( $_SESSION['deleted_datalayer_property'] );
}
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global', 'core', 'admin' )->tabs(
static::$tabs,
$tab,
$output,
\IPS\Http\Url::internal( 'app=core&module=settings&controller=dataLayer', 'admin' )
);
}
}
/**
* Gets the general settings form
*
* @return string
*/
protected function main()
{
$form = new \IPS\Helpers\Form( 'datalayer_general' );
$form->addHeader('Settings');
$form->add( new \IPS\Helpers\Form\YesNo( 'core_datalayer_enabled', \IPS\Settings::i()->core_datalayer_enabled, false ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'core_datalayer_include_pii', \IPS\Settings::i()->core_datalayer_include_pii, false, array( 'togglesOn' => [ 'datalayer_general_core_datalayer_member_pii_choice' ] ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'core_datalayer_member_pii_choice', \IPS\Settings::i()->core_datalayer_member_pii_choice, false ) );
try
{
$default = \IPS\Login\Handler::load( \IPS\Settings::i()->core_datalayer_replace_with_sso );
}
catch ( \OutOfRangeException $e )
{
$default = 0;
}
$form->add( new \IPS\Helpers\Form\Node( 'core_datalayer_replace_with_sso', $default, false, array(
'class' => '\IPS\Login\Handler',
'zeroVal' => 'Use internal Member ID (default)',
'where' => array(
['login_enabled=?', 1],
['login_classname LIKE ?', '%Login_Handler%'],
['login_classname NOT LIKE ?', '%Login_Handler_Standard']
),
) ) );
$form->addHeader('enhancements__core_GoogleTagManager');
$form->addMessage( 'use_gtm_for_installation', 'ipsMessage ipsMessage_info ipsMargin' );
$form->add( new \IPS\Helpers\Form\YesNo( 'core_datalayer_use_gtm', \IPS\Settings::i()->core_datalayer_use_gtm, false ) );
$gtmkey = explode( '.', \IPS\Settings::i()->core_datalayer_gtmkey );
$form->add( new \IPS\Helpers\Form\Text(
'core_datalayer_gtmkey',
array_pop( $gtmkey ),
false,
array(),
function( $_value ) {
/* Check for invalid characters */
if ( preg_match( '/[^a-z,A-Z,0-9,\_]/', $_value ) )
{
throw new \InvalidArgumentException( 'The variable name can only contain alphanumeric characters and underscores.' );
}
elseif ( preg_match( '/[0-9]/', \mb_substr( $_value, 0, 1 ) ) )
{
throw new \InvalidArgumentException( 'The variable name cannot start with a number.' );
}
/* Is a custom handler using this? Note that, in JS, window.variable and variable are nearly always the same */
$where = [
[ 'enabled=?', 1 ],
[ 'use_js=?', 1 ],
\IPS\Db::i()->in( 'datalayer_key', [ $_value, "window.$_value" ] ),
];
if (
class_exists( 'IPS\cloud\DataLayer' ) AND
\IPS\core\DataLayer::i() instanceof \IPS\cloud\DataLayer AND
\count( \IPS\Db::i()->select( 'datalayer_key', \IPS\cloud\DataLayer\Handler::$databaseTable, $where, null, 1 ) )
)
{
throw new \InvalidArgumentException( "$_value or window.$_value is in use by a custom handler." );
}
},
'<span class="ipsType_monospace">window.</span>'
));
if ( $values = $form->values() )
{
\IPS\Session::i()->csrfCheck();
if ( isset( $values['core_datalayer_replace_with_sso'] ) )
{
$values['core_datalayer_replace_with_sso'] = $values['core_datalayer_replace_with_sso'] ? $values['core_datalayer_replace_with_sso']->_id : 0;
}
/* Prefix the gtmkey with window. */
if ( isset( $values['core_datalayer_gtmkey'] ) )
{
$values['core_datalayer_gtmkey'] = "window.{$values['core_datalayer_gtmkey']}";
}
/* Since GTM is a handler, cachebust the handler for template if it was updated */
foreach( ['core_datalayer_use_gtm', 'core_datalayer_gtmkey', 'googletag_head_code', 'googletag_noscript_code'] as $input )
{
if ( isset( $values[$input] ) )
{
$key = \IPS\core\DataLayer\Handler::$handlerCacheKey;
unset( \IPS\Data\Store::i()->$key );
break;
}
}
/* Clear the cached configuration since we're changing it */
\IPS\core\DataLayer::i()->clearCachedConfiguration([ '_jsConfig', '_eventProperties', '_propertyEvents' ]);
\IPS\Settings::i()->changeValues( $values );
/* Redirect to avoid csrfKey errors */
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=settings&controller=dataLayer&tab=main' ) );
}
return "<br>$form";
}
/**
* Property settings and configuration
*
* @return string
*/
protected function pageContext()
{
/* Get the page level properties */
$_properties = \IPS\core\DataLayer::i()->propertiesConfiguration;
foreach ( $_properties as $k => $property )
{
if ( $property['page_level'] AND $property['enabled'] )
{
$properties[$k] = array(
'name' => $property['formatted_name'],
'type' => $property['type'],
'pii' => $property['pii'],
'custom' => $property['custom'] ?? 0,
'short' => $property['short'],
);
}
}
$propertyBlock = new \IPS\Helpers\Table\Custom(
$properties,
\IPS\Http\Url::internal( 'app=core&module=settings&controller=dataLayer&tab=pageContext' )
);
$propertyBlock->classes[] = 'ipsPadding:none ipsMargin:none';
$propertyBlock->exclude = array('custom', 'description');
$propertyBlock->langPrefix = 'datalayer_';
if ( !\IPS\Request::i()->sortby )
{
$propertyBlock->sortBy = 'name';
}
if ( !\IPS\Request::i()->sortdirection )
{
$propertyBlock->sortDirection = 'asc';
}
$propertyBlock->parsers = array(
'pii' => array( $this, '_yesNo' ),
'name' => function ( $val, $row ) {
$url = \IPS\Http\Url::internal('app=core&module=settings&controller=dataLayer&tab=properties&property_key=' . $val);
$title = $row['short'];
return "<span class='ipsType_monospace ipsMargin:none'><a href='$url' data-ipstooltip title='$title'>$val</a></span>";
},
);
return \IPS\Theme::i()->getTemplate( 'settings', 'core', 'admin' )->dataLayerContext( $propertyBlock );
}
/**
* Property settings and configuration
*
* @return string
*/
protected function properties()
{
/* Load our properties */
$properties = \IPS\core\DataLayer::i()->propertiesConfiguration;
$property_key = \IPS\Request::i()->property_key ?: array_keys( $properties )[0];
/* Do we know the requested property? */
if ( !isset( $properties[$property_key] ) )
{
\IPS\Output::i()->redirect( \IPS\Request::i()->url()->stripQueryString([ 'property_key' ]) );
}
$property = $properties[$property_key];
/* Sidebar */
$propertySelector = \IPS\Theme::i()->getTemplate( 'settings', 'core', 'admin' )->dataLayerSelector(
$properties,
'properties',
'property_key',
'Data Layer Properties',
$property_key,
true,
array( $this, '_truncate' )
);
/* Form */
$form = new \IPS\Helpers\Form( 'datalayer_properties_' . $property_key, 'save', \IPS\Http\Url::internal( 'app=core&module=settings&controller=dataLayer&tab=properties&property_key=' . $property_key ) );
$form->class = 'ipsForm_vertical';
$form->add( new \IPS\Helpers\Form\YesNo( 'datalayer_property_enabled', $property['enabled'], false ) );
$form->add( new \IPS\Helpers\Form\Text( 'datalayer_property_formatted_name', $property['formatted_name'], false, array(), $this->formattedNameValid('properties', $property_key) ) );
if ( $property['custom'] ?? 0 )
{
$form->add( new \IPS\Helpers\Form\Text( 'datalayer_property_value', $property['value'], true ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'datalayer_property_page_level', $property['page_level'], true ) );
$form->add( new \IPS\Helpers\Form\Text( 'datalayer_property_short', $property['short'], false, array( 'maxLength' => 50 ) ) );
$events = \IPS\core\DataLayer::i()->eventConfiguration;
$options = array();
foreach ( $events as $key => $data )
{
$options[$key] = $data['formatted_name'];
}
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'datalayer_property_event_keys', $property['event_keys'], false, array('options' => $options, 'multiple' => 1) ) );
}
/* Update the values as long as the property_key was specified in the request */
if ( ( $values = $form->values() ) AND \IPS\Request::i()->property_key === $property_key )
{
/* Get our submitted values */
$_values = array();
if ( isset( $values['datalayer_property_enabled'] ) )
{
$_values['enabled'] = (bool) $values['datalayer_property_enabled'];
}
if ( isset( $values['datalayer_property_formatted_name'] ) )
{
$_values['formatted_name'] = $values['datalayer_property_formatted_name'];
}
if ( $property['custom'] ?? 0 )
{
foreach ( $values as $field => $value )
{
if ( isset( $_values[$field] ) )
{
continue;
}
$field = str_replace( 'datalayer_property_', '', $field );
$_values[$field] = $value;
}
}
/* Save the property */
if ( !empty( $_values ) )
{
try
{
\IPS\core\DataLayer::i()->savePropertyConfiguration( $property_key, $_values );
}
catch ( \InvalidArgumentException $e ) {}
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=settings&controller=dataLayer&tab=properties&property_key=' . $property_key ) );
}
/* Load our events */
$_events = \IPS\core\DataLayer::i()->getPropertyEvents( $property_key );
$events = array();
foreach( $_events as $key => $event )
{
$events[$key] = array(
'name' => $event['formatted_name'],
'key' => $key,
'enabled' => $event['enabled'],
'short' => $event['short'],
);
}
/* Property's events table */
if ( empty( $events ) )
{
$eventTable = "";
}
else
{
$eventTable = new \IPS\Helpers\Table\Custom(
$events,
\IPS\Http\Url::internal( 'app=core&module=settings&controller=dataLayer&tab=properties&property_key=' . $property_key )
);
if ( !\IPS\Request::i()->sortby )
{
$eventTable->sortBy = 'name';
}
if ( !\IPS\Request::i()->sortdirection )
{
$eventTable->sortDirection = 'asc';
}
$eventTable->langPrefix = 'datalayer_';
$eventTable->exclude = array( 'description', 'key' );
$eventTable->parsers = array(
'name' => function ( $val, $row )
{
$url = \IPS\Http\Url::internal('app=core&module=settings&controller=dataLayer&tab=events&event_key=' . $row['key']);
$title = $row['short'];
return "<span class='ipsMargin:none ipsType_monospace'><a href='$url' data-ipstooltip title='$title'>$val</a></span>";
},
'enabled' => array( $this, '_enabledDisabled' ),
);
}
/* Render Content */
$content = \IPS\Theme::i()->getTemplate( 'settings', 'core', 'admin' )->dataLayerTitleContent(
$property,
$property_key,
'property',
$form,
(string) $eventTable
);
return \IPS\Theme::i()->getTemplate( 'settings', 'core', 'admin' )->dataLayerTab( $propertySelector, $content );
}
/**
* Property settings and configuration
*
* @return string
*/
protected function addProperty()
{
\IPS\Output::i()->title = 'Add property';
$form = new \IPS\Helpers\Form( 'datalayer_add_property' );
$form->add( new \IPS\Helpers\Form\Text( 'datalayer_property_formatted_name', null, true, array(), $this->formattedNameValid() ) );
$form->add( new \IPS\Helpers\Form\Text( 'datalayer_property_value', null, true ) );
$form->add( new \IPS\Helpers\Form\TextArea( 'datalayer_property_description', null, true ) );
$form->add( new \IPS\Helpers\Form\Text( 'datalayer_property_short', null, false, array( 'maxLength' => 50 ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'datalayer_property_page_level', 1, true ) );
$events = \IPS\core\DataLayer::i()->eventConfiguration;
$options = array();
foreach ( $events as $key => $data )
{
$options[$key] = $data['formatted_name'];
}
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'datalayer_property_event_keys', array(), false, array('options' => $options, 'multiple' => 1) ) );
if ( $values = $form->values() )
{
\IPS\Session::i()->csrfCheck();
$_values = array();
foreach ( $values as $field => $value )
{
$field = str_replace( 'datalayer_property_', '', $field );
$_values[$field] = $value;
}
$values = $_values;
$values['enabled'] = true;
$key = $values['formatted_name'];
\IPS\core\DataLayer::i()->savePropertyConfiguration( $key, $values, true );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=settings&controller=dataLayer&tab=properties&property_key=' . $key ) );
}
\IPS\Output::i()->output = (string) $form;
}
/**
* Delete a property
*/
protected function deleteProperty()
{
\IPS\Session::i()->csrfCheck();
$key = \IPS\Request::i()->property_key;
$propertiesUrl = \IPS\Http\Url::internal( 'app=core&module=settings&controller=dataLayer&tab=properties' );
if ( empty( $key ) )
{
\IPS\Output::i()->redirect( $propertiesUrl );
}
$properties = \IPS\core\DataLayer::i()->propertiesConfiguration;
if ( isset( $properties[$key] ) )
{
if ( $properties[$key]['custom'] ?? 0 )
{
$setting = json_decode( \IPS\Settings::i()->core_datalayer_properties, true ) ?: array();
unset( $setting[$key] );
\IPS\Settings::i()->changeValues([ 'core_datalayer_properties' => json_encode( $setting ) ]);
\IPS\core\DataLayer::i()->clearCachedConfiguration();
$_SESSION['deleted_datalayer_property'] = 1;
}
else
{
$propertiesUrl = $propertiesUrl->setQueryString([ 'property_key' => $key ]);
}
}
\IPS\Output::i()->redirect( $propertiesUrl );
}
/**
* Event settings and configuration
*
* @return string
*/
protected function events()
{
/* Load our events */
$events = \IPS\core\DataLayer::i()->eventConfiguration;
$event_key = \IPS\Request::i()->event_key ?: 'content_create';
/* Do we know the recognized one? */
if ( !isset( $events[$event_key] ) )
{
\IPS\Output::i()->redirect( \IPS\Request::i()->url()->stripQueryString([ 'event_key' ]) );
}
$event = $events[$event_key];
foreach ( array_keys( $events ) as $_eventKey )
{
$events[$_eventKey]['description'] = 'Fires when ' . $events[$_eventKey]['description'];
}
/* Create our sidebar */
$eventSelector = \IPS\Theme::i()->getTemplate( 'settings', 'core', 'admin' )->dataLayerSelector(
$events,
'events',
'event_key',
'Data Layer Events',
$event_key,
true,
array( $this, '_truncate' )
);
/* Form */
$form = new \IPS\Helpers\Form( 'datalayer_events_' . $event_key, 'save', \IPS\Http\Url::internal( 'app=core&module=settings&controller=dataLayer&tab=events&event_key=' . $event_key ) );
$form->class = 'ipsForm_vertical';
$form->add( new \IPS\Helpers\Form\YesNo( 'datalayer_event_enabled', $event['enabled'], false ) );
$form->add( new \IPS\Helpers\Form\Text( 'datalayer_event_formatted_name', $event['formatted_name'], false, array(), $this->formattedNameValid( 'events', $event_key ) ) );
/* We only want to change values when a real event is specified in the request */
if ( ( $values = $form->values() ) AND \IPS\Request::i()->event_key === $event_key )
{
/* Pull out the values to use */
$_values = array();
if ( isset( $values['datalayer_event_enabled'] ) )
{
$_values['enabled'] = (bool) $values['datalayer_event_enabled'];
}
if ( isset( $values['datalayer_event_formatted_name'] ) )
{
$_values['formatted_name'] = $values['datalayer_event_formatted_name'];
}
/* Save the event */
if ( !empty( $_values ) )
{
try
{
\IPS\core\DataLayer::i()->saveEventConfiguration( $event_key, $_values );
}
catch ( \InvalidArgumentException $e ) {}
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=settings&controller=dataLayer&tab=events&event_key=' . $event_key ) );
}
/* Load our properties */
$_properties = \IPS\core\DataLayer::i()->getEventProperties( $event_key );
$properties = array();
foreach( $_properties as $key => $property )
{
$properties[$key] = array(
'name' => $property['formatted_name'],
'key' => $key,
'type' => $property['type'],
'enabled' => $property['enabled'],
'pii' => $property['pii'],
'custom' => $property['custom'] ?? 0,
'short' => $property['short'],
);
}
/* Create our neat properties table */
if ( empty( $properties ) )
{
$propertyTable = "";
}
else
{
$propertyTable = new \IPS\Helpers\Table\Custom(
$properties,
\IPS\Http\Url::internal( 'app=core&module=settings&controller=dataLayer&tab=events&event_key=' . $event_key )
);
if ( !\IPS\Request::i()->sortby )
{
$propertyTable->sortBy = 'name';
}
if ( !\IPS\Request::i()->sortdirection )
{
$propertyTable->sortDirection = 'asc';
}
$propertyTable->langPrefix = 'datalayer_';
$propertyTable->exclude = array( 'custom', 'short', 'key' );
$propertyTable->parsers = array(
'enabled' => array( $this, '_enabledDisabled' ),
'pii' => array( $this, '_yesNo' ),
'name' => function ( $val, $row )
{
$url = \IPS\Http\Url::internal( 'app=core&module=settings&controller=dataLayer&tab=properties&property_key=' . $row['key'] );
$title = $row['short'];
return "<pre class='ipsMargin:none'><a href='$url' data-ipstooltip title='$title' >$val</a></pre>";
},
);
}
/* Render Content */
$event['description'] = "Fires when {$event['description']}";
$content = \IPS\Theme::i()->getTemplate( 'settings', 'core', 'admin' )->dataLayerTitleContent(
$event,
$event_key,
'event',
(string) $form,
(string) $propertyTable
);
return \IPS\Theme::i()->getTemplate( 'settings', 'core', 'admin' )->dataLayerTab( $eventSelector, $content );
}
/**
* Is the value a valid property or event formatted name?
*
* @param string $group Either properties or events; the datalayer collection to test the key against
* @param ?string $current The key of the property/event that currently has this value set as its formatted name
*
* @return callback
*/
public function formattedNameValid( string $group='properties', ?string $current=null )
{
return function( $value ) use ( $group, $current )
{
if ( $group === 'properties' AND \strtolower( $value ) === 'event' )
{
throw new \InvalidArgumentException( "You cannot name a property '$value' because that key is reserved" );
}
if ( preg_match( '/[^a-z,A-Z,\_]/', $value ) )
{
throw new \InvalidArgumentException( 'The name can only contain letters and underscores.' );
}
$collection = ( $group === 'events' ) ? \IPS\core\DataLayer::i()->eventConfiguration : \IPS\core\DataLayer::i()->propertiesConfiguration;
foreach ( $collection as $key => $data )
{
if ( $key === $current )
{
continue;
}
if ( isset( $data['formatted_name'] ) AND $data['formatted_name'] === $value )
{
throw new \InvalidArgumentException( 'This Data Layer name is already in use' );
}
}
};
}
/**
* Formatter method to make code cleaner
*
* @param mixed $val
*
* @return string
*/
public function _enabledDisabled( $val, $row=null )
{
return $val ? 'Enabled' : 'Disabled';
}
/**
* Formatter method to make code cleaner
*
* @param mixed $val
*
* @return string
*/
public function _yesNo( $val, $row=null )
{
return $val ? 'Yes' : 'No';
}
/**
* Truncates the string and removes tags. Ends in ... if the string is longer than the limit
*
* @param string $_string The string to truncate
* @param int $length The desired length
*
* @return string
*/
public function _truncate( $_string, $length=100 )
{
$_string = strip_tags( $_string );
$string = \substr( $_string, 0, $length );
$changedLength = ( \strlen( $_string ) !== \strlen( $string ) );
$string = trim( $string, ". \n\r\t\v\x00" );
if ( $changedLength )
{
$string = \trim( \substr( $string, 0, $length - 3 ), ". \n\r\t\v\x00" ) . '...';
}
return $string;
}
}
@@ -71,7 +71,7 @@ class _email extends \IPS\Dispatcher\Controller
$method = \IPS\Settings::i()->mail_method;
$disabled = array();
if ( \IPS\Settings::i()->sendgrid_api_key )
if ( \IPS\Settings::i()->sendgrid_api_key AND \IPS\Settings::i()->sendgrid_use_for > 0 )
{
if ( \IPS\Settings::i()->sendgrid_use_for == 2 )
{
@@ -88,7 +88,7 @@ class _email extends \IPS\Dispatcher\Controller
{
$method = 'php';
}
$settingsForm->add( new \IPS\Helpers\Form\Radio( 'mail_method', $method, TRUE, array(
'options' => array( 'php' => ( \IPS\CIC ? 'mail_method_cic' : 'mail_method_php' ), 'smtp' => 'mail_method_smtp', 'sendgrid' => 'mail_method_sendgrid' ),
'disabled' => $disabled,
@@ -57,10 +57,14 @@ class _general extends \IPS\Dispatcher\Controller
$form->add( new \IPS\Helpers\Form\Text( 'site_twitter_id', \IPS\Settings::i()->site_twitter_id, FALSE, array( 'placeholder' => \IPS\Member::loggedIn()->language()->addToStack('site_twitter_id_placeholder'), 'size' => 20 ) ) );
$form->add( new \IPS\Helpers\Form\Translatable( 'copyright_line', NULL, FALSE, array( 'app' => 'core', 'key' => 'copyright_line_value', 'placeholder' => \IPS\Member::loggedIn()->language()->addToStack('copyright_line_placeholder') ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'relative_dates_enable', \IPS\Settings::i()->relative_dates_enable, FALSE ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'site_site_elsewhere', \IPS\Settings::i()->site_site_elsewhere, FALSE, [ 'togglesOn' => [ 'site_main_url', 'site_main_title' ] ] ) );
$form->add( new \IPS\Helpers\Form\Url( 'site_main_url', \IPS\Settings::i()->site_main_url, FALSE, [], NULL, NULL, NULL, 'site_main_url' ) );
$form->add( new \IPS\Helpers\Form\Text( 'site_main_title', \IPS\Settings::i()->site_main_title, FALSE, [], NULL, NULL, NULL, 'site_main_title' ) );
/* $form->add( new \IPS\Helpers\Form\CheckboxSet( 'diagnostics_reporting', array( 'usage' => ( (bool) \IPS\Settings::i()->usage_reporting ), 'diagnostics' => ( (bool) \IPS\Settings::i()->diagnostics_reporting ) ), FALSE, array( 'options' => array(
'usage' => 'diagnostics_reporting_usage',
'diagnostics' => 'diagnostics_reporting_diagnostics',
) ) ) ); */
) ) ) );
*/
if ( $values = $form->values() )
{
@@ -76,9 +80,9 @@ class _general extends \IPS\Dispatcher\Controller
$values['site_address'] = json_encode( $values['site_address'] );
// $values['usage_reporting'] = \in_array( 'usage', $values['diagnostics_reporting'] );
// $values['diagnostics_reporting'] = \in_array( 'diagnostics', $values['diagnostics_reporting'] );
// \IPS\Db::i()->update( 'core_tasks', array( 'enabled' => \intval( $values['usage_reporting'] ) ), array( '`key`=?', 'usagereporting' ) );
//$values['usage_reporting'] = \in_array( 'usage', $values['diagnostics_reporting'] );
//$values['diagnostics_reporting'] = \in_array( 'diagnostics', $values['diagnostics_reporting'] );
//\IPS\Db::i()->update( 'core_tasks', array( 'enabled' => \intval( $values['usage_reporting'] ) ), array( '`key`=?', 'usagereporting' ) );
$form->saveAsSettings( $values );
@@ -100,7 +100,14 @@ class _licensekey extends \IPS\Dispatcher\Controller
unset( \IPS\Data\Store::i()->license_data );
/* Return the overview screen afterwards */
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=settings&controller=licensekey' ), 'license_key_refreshed' );
if ( isset( \IPS\Request::i()->return ) and \IPS\Request::i()->return === 'cloud' and \IPS\Application::appIsEnabled('cloud') )
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=cloud&module=smartcommunity&controller=smartcommunity' ), 'cloud_license_refreshed' );
}
else
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=settings&controller=licensekey' ), 'license_key_refreshed' );
}
}
/**
@@ -308,7 +308,24 @@ class _posting extends \IPS\Dispatcher\Controller
$form->add( new \IPS\Helpers\Form\YesNo( 'links_external', \IPS\Settings::i()->links_external ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'posts_add_nofollow', \IPS\Settings::i()->posts_add_nofollow, FALSE, array( 'togglesOn' => array( 'posts_add_nofollow_exclude' ) ), NULL, NULL, NULL, 'posts_add_nofollow' ) );
$form->add( new \IPS\Helpers\Form\Stack( 'posts_add_nofollow_exclude', \IPS\Settings::i()->posts_add_nofollow_exclude ? json_decode( \IPS\Settings::i()->posts_add_nofollow_exclude ) : array(), FALSE, array( 'placeholder' => 'example.com' ), NULL, NULL, NULL, 'posts_add_nofollow_exclude' ) );
$form->add( new \IPS\Helpers\Form\Radio( 'email_filter_action', \IPS\Settings::i()->email_filter_action, FALSE, array(
'options' => array(
'allow' => 'email_filter_action_allow',
'moderate' => 'email_filter_action_moderate',
'replace' => 'email_filter_action_replace'
),
'description' => array(
'allow' => 'email_filter_action_allow_desc',
'moderate' => 'email_filter_action_moderate_desc',
'replace' => 'email_filter_action_replace_desc'
),
'toggles' => array( 'replace' => array( 'email_filter_replace_text' ) )
), NULL, NULL, NULL, 'email_filter_action' ) );
$form->add( new \IPS\Helpers\Form\Text( 'email_filter_replace_text', \IPS\Settings::i()->email_filter_replace_text, FALSE, array(), NULL, NULL, NULL, 'email_filter_replace_text' ) );
/* Save values */
if ( $values = $form->values() )
{
@@ -770,21 +787,6 @@ class _posting extends \IPS\Dispatcher\Controller
/* And redirect */
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=settings&controller=posting&tab=acronymExpansion" ) );
}
/**
* Rebuild attachment thumbnails
*
* @return void
*/
public function rebuildThumbnails()
{
\IPS\Session::i()->csrfCheck();
\IPS\Task::queue( 'core', 'RebuildAttachmentThumbnails', array(), 4 );
/* And redirect */
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=settings&controller=posting" ), 'thumbnails_rebuilt' );
}
/**
* Rebuild URL refs
@@ -184,8 +184,13 @@ class _moderators extends \IPS\Dispatcher\Controller
}
if ( $values = $form->values() )
{
{
$rowId = NULL;
if( !isset( $values['moderators_type'] ) )
{
$values['moderators_type'] = isset( $values['moderators_group'] ) ? 'g' : 'm';
}
if ( $values['moderators_type'] === 'g' or !\IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'staff', 'moderators_add_member' ) )
{
+244 -18
View File
@@ -23,6 +23,11 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
*/
class _badges extends \IPS\Dispatcher\Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* @brief Allow MySQL RW separation for efficiency
*/
@@ -40,32 +45,253 @@ class _badges extends \IPS\Dispatcher\Controller
}
/**
* Points earned activity chart
* Show badges in a modal
*
* @return void
*/
protected function showBadges()
{
$where = [
[ 'datetime BETWEEN ? AND ?', \IPS\Request::i()->badgeDateStart, \IPS\Request::i()->badgeDateEnd ],
[ 'member_group_id=?', \IPS\Request::i()->member_group_id ]
];
$query = \IPS\Db::i()->select( 'badge, COUNT(*) as count', 'core_member_badges', $where, NULL, NULL, 'badge' )
->join( 'core_members', [ 'core_members.member_id=core_member_badges.member' ] );
$results = [];
foreach( $query as $row )
{
try
{
$row['badge'] = \IPS\core\Achievements\Badge::load( $row['badge'] );
$results[] = $row;
}
catch( \Exception $e ) { }
}
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'achievements' )->statsBadgeModal( $results );
}
/**
* Show member badges in a modal
*
* @return void
*/
protected function showMemberBadges()
{
$where = [
[ 'datetime BETWEEN ? AND ?', \IPS\Request::i()->badgeDateStart, \IPS\Request::i()->badgeDateEnd ],
[ 'member_id=?', \IPS\Request::i()->member_id ]
];
$query = \IPS\Db::i()->select( 'badge, datetime', 'core_member_badges', $where, NULL, NULL )
->join( 'core_members', [ 'core_members.member_id=core_member_badges.member' ] );
$results = [];
foreach( $query as $row )
{
try
{
$row['badge'] = \IPS\core\Achievements\Badge::load( $row['badge'] );
$results[] = $row;
}
catch( \Exception $e ) { }
}
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'achievements' )->statsMemberBadgeModal( $results );
}
/**
* Badges earned activity chart
*
* @return void
*/
protected function manage()
{
$chart = new \IPS\Helpers\Chart\Database( \IPS\Http\Url::internal( 'app=core&module=stats&controller=badges' ), 'core_member_badges', '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' );
$tabs = array(
'type' => 'stats_badges_by_badge',
'list' => 'stats_badges_by_group',
'member' => 'stats_badges_by_member',
);
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('stats_badges_title');
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart' );
$chart->enableHourly = FALSE;
$activeTab = ( isset( \IPS\Request::i()->tab ) and array_key_exists( \IPS\Request::i()->tab, $tabs ) ) ? \IPS\Request::i()->tab : 'type';
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack('stats_member_badges'), 'number', 'COUNT(*)', TRUE );
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->showIntervals = TRUE;
$chart->title = \IPS\Member::loggedIn()->language()->addToStack( 'stats_badges_title' );
$chart->availableTypes = array('AreaChart', 'ColumnChart', 'BarChart');
$chart->enableHourly = FALSE;
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_stats_badges');
\IPS\Output::i()->output = (string) $chart;
$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;
}
elseif ( $activeTab === 'member' )
{
$start = NULL;
$end = NULL;
$defaults = array( 'start' => \IPS\DateTime::create()->setDate( date('Y'), date('m'), 1 ), 'end' => new \IPS\DateTime );
if( isset( \IPS\Request::i()->badgeDateStart ) AND isset( \IPS\Request::i()->badgeDateEnd ) )
{
$defaults = array( 'start' => \IPS\DateTime::ts( \IPS\Request::i()->badgeDateStart ), 'end' => \IPS\DateTime::ts( \IPS\Request::i()->badgeDateEnd ) );
}
$form = new \IPS\Helpers\Form( $activeTab, 'continue' );
$form->add( new \IPS\Helpers\Form\DateRange( 'date', $defaults, TRUE ) );
if( $values = $form->values() )
{
/* Determine start and end time */
$startTime = $values['date']['start']->getTimestamp();
$endTime = $values['date']['end']->getTimestamp();
$start = $values['date']['start']->html();
$end = $values['date']['end']->html();
}
else
{
/* Determine start and end time */
$startTime = $defaults['start']->getTimestamp();
$endTime = $defaults['end']->getTimestamp();
$start = $defaults['start']->html();
$end = $defaults['end']->html();
}
/* Create the table */
$chart = new \IPS\Helpers\Table\Db( 'core_member_badges', \IPS\Http\Url::internal( 'app=core&module=stats&controller=badges&type=member' ), [ 'datetime BETWEEN ? AND ?', $startTime, $endTime ] );
$chart->quickSearch = NULL;
$chart->selects = [ 'count(*) as count' ];
$chart->joins = [
[ 'select' => 'member_id', 'from' => 'core_members', 'where' => 'core_members.member_id=core_member_badges.member' ]
];
$chart->groupBy = 'member_id';
$chart->langPrefix = 'stats_badges_';
$chart->include = [ 'member_id', 'count' ];
$chart->mainColumn = 'member_id';
$chart->baseUrl = $chart->baseUrl->setQueryString( array( 'badgeDateStart' => $startTime, 'badgeDateEnd' => $endTime, 'tab' => $activeTab ) );
$chart->sortBy = 'count';
/* Custom parsers */
$chart->parsers = array(
'member_id' => function( $val, $row )
{
$member = \IPS\Member::load( $val );
return \IPS\Theme::i()->getTemplate( 'global', 'core' )->userPhoto( $member, 'tiny' ) . ' ' . $member->link();
},
'count' => function( $val, $row ) use( $startTime, $endTime )
{
return \IPS\Theme::i()->getTemplate( 'achievements' )->statsMemberBadgeCount( $val, $row['member_id'], $startTime, $endTime );
}
);
$formHtml = $form->customTemplate( array( \IPS\Theme::i()->getTemplate( 'stats' ), 'filtersFormTemplate' ) );
$chart = \IPS\Theme::i()->getTemplate( 'achievements', 'core' )->statsBadgeWrapper( $formHtml, (string) $chart );
}
else
{
$start = NULL;
$end = NULL;
$defaults = array( 'start' => \IPS\DateTime::create()->setDate( date('Y'), date('m'), 1 ), 'end' => new \IPS\DateTime );
if( isset( \IPS\Request::i()->badgeDateStart ) AND isset( \IPS\Request::i()->badgeDateEnd ) )
{
$defaults = array( 'start' => \IPS\DateTime::ts( \IPS\Request::i()->badgeDateStart ), 'end' => \IPS\DateTime::ts( \IPS\Request::i()->badgeDateEnd ) );
}
$form = new \IPS\Helpers\Form( $activeTab, 'continue' );
$form->add( new \IPS\Helpers\Form\DateRange( 'date', $defaults, TRUE ) );
if( $values = $form->values() )
{
/* Determine start and end time */
$startTime = $values['date']['start']->getTimestamp();
$endTime = $values['date']['end']->getTimestamp();
$start = $values['date']['start']->html();
$end = $values['date']['end']->html();
}
else
{
/* Determine start and end time */
$startTime = $defaults['start']->getTimestamp();
$endTime = $defaults['end']->getTimestamp();
$start = $defaults['start']->html();
$end = $defaults['end']->html();
}
/* Create the table */
$chart = new \IPS\Helpers\Table\Db( 'core_member_badges', \IPS\Http\Url::internal( 'app=core&module=stats&controller=badges&type=list' ), [ 'datetime BETWEEN ? AND ?', $startTime, $endTime ] );
$chart->quickSearch = NULL;
$chart->selects = [ 'count(*) as count' ];
$chart->joins = [
[ 'select' => 'member_group_id', 'from' => 'core_members', 'where' => 'core_members.member_id=core_member_badges.member' ]
];
$chart->groupBy = 'member_group_id';
$chart->langPrefix = 'stats_badges_';
$chart->include = [ 'member_group_id', 'count' ];
$chart->mainColumn = 'member_group_id';
$chart->baseUrl = $chart->baseUrl->setQueryString( array( 'badgeDateStart' => $startTime, 'badgeDateEnd' => $endTime, 'tab' => $activeTab ) );
/* Custom parsers */
$chart->parsers = array(
'member_group_id' => function( $val, $row )
{
try
{
return \IPS\Member\Group::load( $val )->formattedName;
}
catch ( \Throwable $e )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'unavailable' );
}
},
'count' => function( $val, $row ) use( $startTime, $endTime )
{
return \IPS\Theme::i()->getTemplate( 'achievements' )->statsBadgeCount( $val, $row['member_group_id'], $startTime, $endTime );
}
);
$formHtml = $form->customTemplate( array( \IPS\Theme::i()->getTemplate( 'stats' ), 'filtersFormTemplate' ) );
$chart = \IPS\Theme::i()->getTemplate( 'achievements', 'core' )->statsBadgeWrapper( $formHtml, (string) $chart );
}
if ( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->output = (string)$chart;
}
else
{
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'menu__core_stats_badges' );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global', 'core' )->tabs( $tabs, $activeTab, (string)$chart, \IPS\Http\Url::internal( "app=core&module=stats&controller=badges" ), 'tab', '', 'ipsPad' );
}
}
}
@@ -0,0 +1,126 @@
<?php
/**
* @brief follow
* @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 10 Sep 2021
*/
namespace IPS\core\modules\admin\stats;
/* 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;
}
/**
* follow
*/
class _follow extends \IPS\Dispatcher\Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* @brief Number of results per page
*/
const PER_PAGE = 25;
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'follow_manage' );
parent::execute();
}
/**
* Manage
*
* @return void
*/
protected function manage()
{
$tabs = array(
'followers' => 'stats_followers_title',
'following' => 'stats_following_title',
);
$activeTab = ( isset( \IPS\Request::i()->tab ) and array_key_exists( \IPS\Request::i()->tab, $tabs ) ) ? \IPS\Request::i()->tab : 'followers';
$where = array( 'core_follow.follow_app=? and core_follow.follow_area=?', 'core', 'member' );
$page = isset( \IPS\Request::i()->page ) ? \intval( \IPS\Request::i()->page ) : 1;
if( $page < 1 )
{
$page = 1;
}
$column = ( $activeTab == "followers" ) ? 'follow_rel_id' : 'follow_member_id';
try
{
$total = \IPS\Db::i()->select( 'COUNT(DISTINCT(core_follow.' . $column . '))', 'core_follow', $where )->join( 'core_members', 'core_members.member_id = core_follow.' . $column )->first();
}
catch ( \UnderflowException $e )
{
$total = 0;
}
if( $total )
{
$select = \IPS\Db::i()->select( 'core_follow.' . $column . ', count(*) as count', 'core_follow', $where, 'count DESC', array( ( $page - 1 ) * static::PER_PAGE, static::PER_PAGE ), $column )->join( 'core_members', 'core_members.member_id = core_follow.' . $column );
$mids = array();
foreach( $select as $row )
{
$mids[] = $row[ $column ];
}
$members = array();
if ( \count( $mids ) )
{
$members = iterator_to_array( \IPS\Db::i()->select( '*', 'core_members', array( \IPS\Db::i()->in( 'member_id', $mids ) ) )->setKeyField('member_id') );
}
$pagination = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->pagination(
\IPS\Http\Url::internal( 'app=core&module=stats&controller=follow' ),
ceil( $total / static::PER_PAGE ),
$page,
static::PER_PAGE,
FALSE
);
$output = \IPS\Theme::i()->getTemplate('stats' )->topFollow( $select, $pagination, $members, $total, $column, $activeTab );
}
else
{
$output= \IPS\Theme::i()->getTemplate( 'global', 'core' )->block( NULL, \IPS\Member::loggedIn()->language()->addToStack('no_results'), FALSE , 'ipsPad', NULL, TRUE );
}
if ( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->paddedBlock( $output, NULL, "ipsPad" );
}
else
{
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'menu__core_stats_follow' );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global', 'core' )->tabs( $tabs, $activeTab, $output, \IPS\Http\Url::internal( "app=core&module=stats&controller=follow" ), 'tab', '', 'ipsPad' );
}
}
// Create new methods with the same name as the 'do' parameter which should execute it
}
@@ -50,6 +50,14 @@ class _membersactivity extends \IPS\Dispatcher\Controller
*/
protected function manage()
{
$tabs = array(
'overview' => 'stats_activity_overview',
'inactive' => 'stats_activity_inactive',
);
$activeTab = ( isset( \IPS\Request::i()->tab ) and array_key_exists( \IPS\Request::i()->tab, $tabs ) ) ? \IPS\Request::i()->tab : 'overview';
$count = NULL;
$table = NULL;
$start = NULL;
@@ -72,7 +80,7 @@ class _membersactivity extends \IPS\Dispatcher\Controller
{
$defaultGroups = array_keys( $groupOptions );
}
$form = new \IPS\Helpers\Form( 'posts', 'continue' );
$form->add( new \IPS\Helpers\Form\DateRange( 'date', $defaults, TRUE ) );
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'groups', $defaultGroups, FALSE, array( 'options' => $groupOptions ), NULL, NULL, NULL, 'group_filters' ) );
@@ -104,7 +112,15 @@ class _membersactivity extends \IPS\Dispatcher\Controller
if( $start AND $end )
{
/* Build our where clause */
$where = array( array( 'member_last_post BETWEEN ? AND ?', $startTime, $endTime ) );
if( $activeTab == 'overview' )
{
$where = array( array( 'member_last_post BETWEEN ? AND ?', $startTime, $endTime ) );
}
else
{
$where = array( array( 'joined BETWEEN ? AND ?', $startTime, $endTime ) );
$where[] = array( 'last_activity = 0' );
}
if( $groups !== NULL )
{
@@ -113,7 +129,7 @@ class _membersactivity extends \IPS\Dispatcher\Controller
/* Get the count */
$count = \IPS\Db::i()->select( 'COUNT(*)', 'core_members', $where )->first();
/* And now build the table */
$table = new \IPS\Helpers\Table\Db( 'core_members', \IPS\Request::i()->url()->setQueryString( array( 'postDateStart' => $startTime, 'postDateEnd' => $endTime, 'postGroups' => \is_array( $groups ) ? implode( ',', $groups ) : NULL ) ), $where );
@@ -126,7 +142,7 @@ class _membersactivity extends \IPS\Dispatcher\Controller
/* Default sort options */
$table->sortBy = $table->sortBy ?: 'member_last_post';
$table->sortDirection = $table->sortDirection ?: 'desc';
/* Custom parsers */
$table->parsers = array(
'name' => function( $val, $row )
@@ -140,12 +156,12 @@ class _membersactivity extends \IPS\Dispatcher\Controller
},
'member_last_post' => function( $val, $row )
{
return \IPS\DateTime::ts( $val )->html();
return ( $val ) ? \IPS\DateTime::ts( $val )->html() : \IPS\Member::loggedIn()->language()->addToStack('never');
},
'group_name' => function( $val, $row )
{
$secondary = \IPS\Member::constructFromData( $row )->groups;
foreach( $secondary as $k => $v )
{
if( $v == $row['member_group_id'] or $v == 0 )
@@ -153,7 +169,7 @@ class _membersactivity extends \IPS\Dispatcher\Controller
unset( $secondary[ $k ] );
continue;
}
$secondary[ $k ] = \IPS\Member\Group::load( $v );
}
@@ -169,13 +185,27 @@ class _membersactivity extends \IPS\Dispatcher\Controller
},
);
$table->extraHtml = \IPS\Theme::i()->getTemplate( 'stats' )->tableheader( $start, $end, $count, "member_activity_results" );
$table->extraHtml = \IPS\Theme::i()->getTemplate( 'stats' )->tableheader( $start, $end, $count, ( $activeTab == "overview" ) ? "member_activity_results" : 'member_activity_results_inactive' );
}
$formHtml = $form->customTemplate( array( \IPS\Theme::i()->getTemplate( 'stats' ), 'filtersFormTemplate' ) );
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'admin_stats.js', 'core' ) );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_stats_membersactivity');
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'stats' )->memberactivity( $formHtml, $count, $table );
$chart = \IPS\Theme::i()->getTemplate( 'stats' )->memberactivity( $formHtml, $count, $table, $activeTab );
if ( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->output = (string) $chart;
}
else
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global', 'core' )->tabs( $tabs, $activeTab, (string) $chart, \IPS\Http\Url::internal( "app=core&module=stats&controller=membersactivity" ), 'tab', '', 'ipsPad' );
}
}
}
@@ -0,0 +1,136 @@
<?php
/**
* @brief moderators
* @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 22 Sep 2021
*/
namespace IPS\core\modules\admin\stats;
/* 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;
}
/**
* moderators
*/
class _moderators extends \IPS\Dispatcher\Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'moderators_manage' );
parent::execute();
}
/**
* Manage
*
* @return void
*/
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;
}
else
{
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_stats_moderators');
\IPS\Output::i()->output = (string) $chart;
}
}
}
@@ -0,0 +1,128 @@
<?php
/**
* @brief preferences
* @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 02 Sep 2021
*/
namespace IPS\core\modules\admin\stats;
/* 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;
}
/**
* preferences
*/
class _preferences extends \IPS\Dispatcher\Controller
{
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'preferences_manage' );
parent::execute();
}
/**
* Manage
*
* @return void
*/
protected function manage()
{
$tabs = array(
'theme' => 'stats_member_pref_theme',
'lang' => 'stats_member_pref_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'] ) );
}
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'] ) );
}
break;
}
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('stats_' . $activeTab . '_title');
$chart = $chart->render('PieChart', array(
'backgroundColor' => '#ffffff',
'pieHole' => 0.4,
'chartArea' => array(
'width' =>"90%",
'height' => "90%"
)
) );
if ( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->paddedBlock( (string) $chart, NULL, "ipsPad" );
}
else
{
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_stats_preferences');
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global', 'core' )->tabs( $tabs, $activeTab, (string) $chart, \IPS\Http\Url::internal( "app=core&module=stats&controller=preferences" ), 'tab', '', 'ipsPad' );
}
}
}
@@ -0,0 +1,151 @@
<?php
/**
* @brief referrals
* @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 Sep 2021
*/
namespace IPS\core\modules\admin\stats;
/* 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;
}
/**
* referrals
*/
class _referrals extends \IPS\Dispatcher\Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* @brief Number of results per page
*/
const PER_PAGE = 25;
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'referrals_manage' );
parent::execute();
}
/**
* Top Referrers
*
* @return void
*/
protected function manage()
{
$where = array();
if ( isset( \IPS\Request::i()->form ) )
{
$form = new \IPS\Helpers\Form( 'form', 'go' );
$default = array(
'start' => \IPS\Request::i()->start ? \IPS\DateTime::ts( \IPS\Request::i()->start ) : NULL,
'end' => \IPS\Request::i()->end ? \IPS\DateTime::ts( \IPS\Request::i()->end ) : NULL
);
$form->add( new \IPS\Helpers\Form\DateRange( 'stats_date_range', $default, FALSE, array( 'start' => array( 'max' => \IPS\DateTime::ts( time() )->setTime( 0, 0, 0 ), 'time' => FALSE ), 'end' => array( 'max' => \IPS\DateTime::ts( time() )->setTime( 23, 59, 59 ), 'time' => FALSE ) ) ) );
if ( !$values = $form->values() )
{
\IPS\Output::i()->output = $form;
return;
}
}
/* Figure out start and end parameters for links */
$params = array(
'start' => !empty( $values['stats_date_range']['start'] ) ? $values['stats_date_range']['start']->getTimestamp() : \IPS\Request::i()->start,
'end' => !empty( $values['stats_date_range']['end'] ) ? $values['stats_date_range']['end']->getTimestamp() : \IPS\Request::i()->end
);
if( $params['start'] )
{
$where[] = array( 'joined>?', $params['start'] );
}
if( $params['end'] )
{
$where[] = array( 'joined<?', $params['end'] );
}
$page = isset( \IPS\Request::i()->page ) ? \intval( \IPS\Request::i()->page ) : 1;
if( $page < 1 )
{
$page = 1;
}
try
{
$total = \IPS\Db::i()->select( 'COUNT(DISTINCT(core_referrals.referred_by))', 'core_referrals', $where )->join( 'core_members', 'core_members.member_id = core_referrals.referred_by')->first();
}
catch ( \UnderflowException $e )
{
$total = 0;
}
if( $total )
{
$select = \IPS\Db::i()->select( 'core_referrals.referred_by as member_id, count(*) as count', 'core_referrals', $where, 'count DESC', array( ( $page - 1 ) * static::PER_PAGE, static::PER_PAGE ), 'member_id' )->join( 'core_members', 'core_members.member_id = core_referrals.referred_by');
$mids = array();
foreach( $select as $row )
{
$mids[] = $row['member_id'];
}
$members = array();
if ( \count( $mids ) )
{
$members = iterator_to_array( \IPS\Db::i()->select( '*', 'core_members', array( \IPS\Db::i()->in( 'member_id', $mids ) ) )->setKeyField('member_id') );
}
$pagination = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->pagination(
\IPS\Http\Url::internal( 'app=core&module=stats&controller=referrals' )->setQueryString( $params ),
ceil( $total / static::PER_PAGE ),
$page,
static::PER_PAGE,
FALSE
);
\IPS\Output::i()->sidebar['actions'] = array(
'settings' => array(
'title' => 'stats_date_range',
'icon' => 'calendar',
'link' => \IPS\Http\Url::internal( 'app=core&module=stats&controller=referrals&form=1' )->setQueryString( $params ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('stats_date_range') )
)
);
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('stats' )->topMembers( $select, $pagination, $members, $total );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_stats_referrals');
}
else
{
/* Return the no results message */
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'global', 'core' )->block( \IPS\Member::loggedIn()->language()->addToStack('menu__core_stats_referrals'), \IPS\Member::loggedIn()->language()->addToStack('no_results'), FALSE , 'ipsPad', NULL, TRUE );
}
}
// Create new methods with the same name as the 'do' parameter which should execute it
}
@@ -40,7 +40,7 @@ class _registrationstats extends \IPS\Dispatcher\Controller
protected function manage()
{
/* Check permission */
\IPS\Dispatcher::i()->checkAcpPermission( 'registrations_manage', 'core', 'members' );
\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,
@@ -0,0 +1,81 @@
<?php
/**
* @brief spam
* @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 01 Sep 2021
*/
namespace IPS\core\modules\admin\stats;
/* 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;
}
/**
* spam
*/
class _spam extends \IPS\Dispatcher\Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'spam_manage' );
parent::execute();
}
/**
* Manage
*
* @return void
*/
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 );
}
);
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_stats_spam');
\IPS\Output::i()->output = (string) $chart;
}
// Create new methods with the same name as the 'do' parameter which should execute it
}
@@ -0,0 +1,112 @@
<?php
/**
* @brief warnings
* @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 20 Sep 2021
*/
namespace IPS\core\modules\admin\stats;
/* 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;
}
/**
* warnings
*/
class _warnings extends \IPS\Dispatcher\Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'warnings_manage' );
parent::execute();
}
/**
* Manage
*
* @return void
*/
protected function manage()
{
$tabs = array(
'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';
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' );
}
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->tableParsers = array(
'wl_date' => function( $val )
{
return (string) \IPS\DateTime::ts( $val );
}
);
if ( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->output = (string) $chart;
}
else
{
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_stats_warnings');
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global', 'core' )->tabs( $tabs, $activeTab, (string) $chart, \IPS\Http\Url::internal( "app=core&module=stats&controller=warnings" ), 'tab', '', 'ipsPad' );
}
}
}
@@ -46,7 +46,7 @@ class _hookedFiles extends \IPS\Dispatcher\Controller
*/
protected function manage()
{
$table = new \IPS\Helpers\Table\Db( 'core_hooks', \IPS\Http\Url::internal( 'app=core&module=support&controller=hookedFiles' ), array( \IPS\Db::i()->in( 'app', \IPS\IPS::$ipsApps, TRUE ) ) );
$table = new \IPS\Helpers\Table\Db( 'core_hooks', \IPS\Http\Url::internal( 'app=core&module=support&controller=hookedFiles' ), array( \IPS\Db::i()->in( 'app', \IPS\IPS::$ipsApps, TRUE ) . " OR app IS NULL" ) );
$table->langPrefix = 'hooks_';
$table->quickSearch = array( 'class', 'hook_class' );
$table->include = array( 'hooks_app', 'hooks_plugin', 'hooks_type', 'hooks_class', 'hooks_file' );
+5 -190
View File
@@ -23,202 +23,17 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
class _sql extends \IPS\Dispatcher\Controller
{
/**
* @brief Has been CSRF-protected
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* @brief Maximum number of results to select
*/
const SELECT_LIMIT = 100;
/**
* Execute
*
* @return void
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'sql_toolbox' );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('sql_toolbox');
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global', 'core' )->message( 'sql_toolbox_warning', 'warning' );
\IPS\Output::i()->breadcrumb[] = array( \IPS\Http\Url::internal( 'app=core&module=support&controller=support' ), \IPS\Member::loggedIn()->language()->addToStack('support') );
\IPS\Output::i()->breadcrumb[] = array( \IPS\Http\Url::internal( 'app=core&module=support&controller=sql' ), \IPS\Member::loggedIn()->language()->addToStack('sql_toolbox') );
parent::execute();
}
/**
* Manage
*
* @return void
*/
protected function manage()
{
/* Get Tables */
$tables = array();
$result = \IPS\Db::i()->query("SHOW TABLE STATUS;");
while ( $table = $result->fetch_assoc() )
{
$tables[] = array(
'name' => $table['Name'],
'rows' => $table['Rows'],
'engine' => $table['Engine'],
);
}
/* Create the table */
$table = new \IPS\Helpers\Table\Custom( $tables, \IPS\Http\Url::internal( 'app=core&module=support&controller=sql' ) );
$table->langPrefix = 'sql_table_';
$table->sortBy = $table->sortBy ?: 'name';
$table->sortDirection = $table->sortDirection ?: 'asc';
$table->quickSearch = 'name';
$table->limit = 200;
/* Buttons */
$table->rowButtons = function( $row )
{
return array(
'view' => array(
'icon' => 'search',
'title' => 'view',
'link' => \IPS\Http\Url::internal( 'app=core&module=support&controller=sql&do=query&action=view' )->setQueryString( 'table', $row['name'] )
),
);
};
/* Query form */
$form = new \IPS\Helpers\Form( 'form', 'run_query', \IPS\Http\Url::internal( 'app=core&module=support&controller=sql&do=query' ) );
$form->class = 'ipsForm_vertical';
$form->add( new \IPS\Helpers\Form\TextArea( 'sql_toolbox_query', NULL, TRUE, array( 'class' => 'ipsField_codeInput' ) ) );
/* Display */
\IPS\Output::i()->output .= $form->customTemplate( array( \IPS\Theme::i()->getTemplate( 'support' ), 'queryFormTemplate' ) ) . (string) $table;
}
/**
* Run Query
*
* @return void
*/
protected function query()
public function manage()
{
/* Select from table */
$query = NULL;
$table = NULL;
if ( isset( \IPS\Request::i()->table ) AND isset( \IPS\Request::i()->action ) )
{
$table = \IPS\Db::i()->escape_string( \IPS\Request::i()->table );
if ( \IPS\Request::i()->action === 'count' )
{
$query = \IPS\Db::i()->select( 'COUNT(*)', $table )->returnFullQuery();
}
else
{
$query = \IPS\Db::i()->select( '*', $table )->returnFullQuery();
}
}
/* Query form */
$valuesFound = FALSE;
$form = new \IPS\Helpers\Form( 'form', 'run_query' );
$form->class = 'ipsForm_vertical';
$form->add( new \IPS\Helpers\Form\TextArea( 'sql_toolbox_query', $query, TRUE, array( 'class' => 'ipsField_codeInput' ) ) );
if ( $values = $form->values() )
{
$query = $values['sql_toolbox_query'];
$valuesFound = TRUE;
}
/* Run */
$queries = array();
$results = array();
if ( $query )
{
/* Run each one */
$queries = preg_split( "/;[\r\n|\n]+/", $query, -1, PREG_SPLIT_NO_EMPTY );
foreach ( $queries as $k => $q )
{
try
{
/* Strip quotes */
$q = trim( preg_replace( '@(--.*)|(#.*)|(((/\*)+?[\w\W]+?(\*/)+))@', '', $q ) );
/* Check it's okay */
if ( preg_match( "/^(DROP|FLUSH)/i", $q ) )
{
throw new \DomainException( 'sql_toolbox_not_allowed' );
}
if ( preg_match( "/^(?!SELECT)/i", preg_replace( "#\s{1,}#s", "", $q ) ) and ( preg_match( "/admin_login_logs/i", preg_replace( "#\s{1,}#s", "", $q ) ) || preg_match( "/admin_permission_rows/i", preg_replace( "#\s{1,}#s", "", $q ) ) ) )
{
throw new \DomainException( 'sql_toolbox_not_allowed' );
}
/* Run it */
$result = \IPS\Db::i()->query( $q );
/* Do we have results */
if ( $result instanceof \mysqli_result )
{
if ( $result->num_rows )
{
/* Paginate */
$pagination = NULL;
if ( $result->num_rows > static::SELECT_LIMIT and !preg_match( "/^EXPLAIN/i", $q ) and !preg_match( "/^SHOW/i", $q ) and !preg_match( "/LIMIT[ 0-9,]+$/i", $q ) )
{
$page = isset( \IPS\Request::i()->page ) ? \intval( \IPS\Request::i()->page ) : 1;
if( $page < 1 OR $valuesFound )
{
$page = 1;
}
$table = ( $result->fetch_field()->table !== FALSE ) ? $result->fetch_field()->table : $table;
$paginationUrl = \IPS\Http\Url::internal( 'app=core&module=support&controller=sql&do=query&action=view' );
if ( isset( $table ) )
{
$paginationUrl = $paginationUrl->setQueryString( 'table', $table );
}
$pagination = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->pagination(
$paginationUrl,
ceil( $result->num_rows / static::SELECT_LIMIT ),
$page,
static::SELECT_LIMIT
);
if( mb_substr( $q, -1, 1 ) == ";" )
{
$q = mb_substr( $q, 0, -1 );
}
$q .= ' LIMIT ' . ( ( $page - 1 ) * static::SELECT_LIMIT ) . ', ' . static::SELECT_LIMIT;
$result = \IPS\Db::i()->query( $q );
}
$results[ $k ] = \IPS\Theme::i()->getTemplate( 'support' )->table( $result, $pagination );
}
else
{
$results[ $k ] = \IPS\Theme::i()->getTemplate( 'global' )->message( 'no_results', 'info' );
}
}
/* Nope, just a success message */
else
{
$results[ $k ] = \IPS\Theme::i()->getTemplate( 'global' )->message( \IPS\Db::i()->info ?: \IPS\Member::loggedIn()->language()->addToStack('sql_toolbox_done'), 'success' );
}
}
catch ( \Exception $e )
{
$results[ $k ] = \IPS\Theme::i()->getTemplate( 'global' )->message( $e->getMessage(), 'error' );
}
}
}
/* Display */
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'support' )->toolboxResults( $form->customTemplate( array( \IPS\Theme::i()->getTemplate( 'support' ), 'queryFormTemplate' ) ), $queries, $results );
// Intentionally blank
}
}
@@ -59,11 +59,82 @@ class _support extends \IPS\Dispatcher\Controller
$form = new \IPS\Helpers\Form( 'form', 'continue' );
$form->class = 'ipsForm_vertical';
$form->add( new \IPS\Helpers\Form\Text( 'support_advice_search', NULL, NULL, array( 'placeholder' => \IPS\Member::loggedIn()->language()->addToStack('health__guides_form') ), NULL, NULL, NULL, 'support_advice_search' ) );
$hooks = \IPS\Db::i()->select( 'COUNT(*)', 'core_hooks', array( \IPS\Db::i()->in( 'app', \IPS\IPS::$ipsApps, TRUE ) . ' OR app IS NULL' ) )->first();
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('get_support');
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'support/dashboard.css', 'core', 'admin' ) );
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js('admin_support.js', 'core', 'admin') );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'support' )->dashboard( $this->_getBlocks(), (string) $this->getLogChart(), $form, $this->_getFeaturedGuides(), $this->_getBulletins() );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'support' )->dashboard( $this->_getBlocks(), (string) $this->getLogChart(), $form, $this->_getFeaturedGuides(), $this->_getBulletins(), $hooks );
}
/**
* Nulled: delete logs
*
* @return void
*/
public function clearLogs() {
$form = new \IPS\Helpers\Form( 'form', 'nulled_delete_logs' );
$options = array(
'system' => \IPS\Member::loggedIn()->language()->addToStack('health_system_log_title'),
'error' => \IPS\Member::loggedIn()->language()->addToStack('health_error_log_title'),
'email' => \IPS\Member::loggedIn()->language()->addToStack('health_email_error_log_title')
);
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'nulled_delete_logs', '', TRUE, array( 'options' => $options, 'multiple' => TRUE, 'impliedUnlimited' => TRUE ) ) );
if ( $values = $form->values() )
{
foreach ($values['nulled_delete_logs'] as $key => $value) {
if ( $value == 'system' ) {
try
{
\IPS\Db::i()->delete( "core_log" );
}
catch( \Exception $e )
{
\IPS\Log::log('Truncate table core_log filed');
}
}
elseif( $value == 'error' ) {
try
{
\IPS\Db::i()->delete( "core_error_logs" );
}
catch( \Exception $e )
{
\IPS\Log::log('Truncate table core_error_logs filed');
}
}
else {
try
{
\IPS\Db::i()->delete( "core_mail_error_logs" );
}
catch( \Exception $e )
{
\IPS\Log::log('Truncate table core_mail_error_logs filed');
}
}
}
if ( \IPS\Request::i()->isAjax() )
{
return;
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=support&controller=systemLogs' ), 'deleted' );
}
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('nulled_delete_logs');
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('global')->block( 'nulled_delete_logs', $form, FALSE );
}
/**
@@ -73,7 +144,21 @@ class _support extends \IPS\Dispatcher\Controller
*/
protected function _getFeaturedGuides()
{
return array();
try
{
$response = \IPS\Http\Url::ips( 'guides' )->setQueryString( 'featured', 1 )->request()->get();
if( $response->httpResponseCode !== 200 )
{
throw new \IPS\Http\Request\Exception;
}
return $response->decodeJson();
}
catch( \IPS\Http\Request\Exception $e )
{
return array();
}
}
/**
@@ -105,6 +190,27 @@ class _support extends \IPS\Dispatcher\Controller
continue;
}
/* If not cached in the last hour, check it still exists */
try
{
if( ( time() - $bulletin['cached'] ) > 3600 )
{
$request = \IPS\Http\Url::ips("bulletin/{$bulletin['id']}")->request( 2 )->get();
switch( (int) $request->httpResponseCode )
{
case 410:
\IPS\Db::i()->delete( 'core_ips_bulletins', [ 'id=?', $bulletin['id'] ] );
continue 2;
break;
default:
\IPS\Db::i()->update( 'core_ips_bulletins', [ 'cached' => ( time() + 3600 - 900 ) ], [ 'id=?', $bulletin['id'] ] );
break;
}
}
}
catch( \IPS\Http\Request\Exception $e ) {}
/* If we have conditions, process them */
if( $bulletin['conditions'] )
{
@@ -358,7 +464,7 @@ class _support extends \IPS\Dispatcher\Controller
catch ( \Exception $ex )
{
\IPS\Log::log( $ex, 'create_vapid_keys' );
\IPS\Output::i()->error( '', '2C338/4', 403, \IPS\Member::loggedIn()->language()->addToStack('health_vapid_key_check_fail_exception', NULL, [ 'sprintf' => $ex->getMessage() ] ) );
\IPS\Output::i()->error( '', '2C338/4', 403, \IPS\Member::loggedIn()->language()->addToStack( 'health_vapid_key_check_fail_exception', FALSE, [ 'sprintf' => $ex->getMessage() ] ) );
}
}
@@ -890,12 +996,19 @@ class _support extends \IPS\Dispatcher\Controller
}
/**
* Clear caches
* Clear caches for nulled
*
* @return void
*/
protected function clearCaches()
protected function clearCachesNull()
{
/* Check CSRF Key*/
\IPS\Session::i()->csrfCheck();
/* URL for redirect */
$md = \IPS\Request::i()->md;
$ct = \IPS\Request::i()->ct;
/* Clear JS Maps first */
\IPS\Output::clearJsFiles();
@@ -922,19 +1035,50 @@ class _support extends \IPS\Dispatcher\Controller
}
else
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=support&controller=support' ) );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module={$md}&controller={$ct}" ), 'nulled_clear_caches_done' );
}
}
/**
* Submit a support ticket
* Clear caches
*
* @return void
* @return void
*/
public function ticket()
protected function clearCaches()
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'support', 'core', 'admin' )->message( '__ipbmafia__nulling_null_alert', 'warning' );
return;
/* Check CSRF Key*/
\IPS\Session::i()->csrfCheck();
/* Clear JS Maps first */
\IPS\Output::clearJsFiles();
/* Reset theme maps to make sure bad data hasn't been cached by visits mid-setup */
\IPS\Theme::deleteCompiledCss();
\IPS\Theme::deleteCompiledResources();
foreach( \IPS\Theme::themes() as $id => $set )
{
/* Invalidate template disk cache */
$set->cache_key = md5( microtime() . mt_rand( 0, 1000 ) );
$set->save();
}
\IPS\Data\Store::i()->clearAll();
\IPS\Data\Cache::i()->clearAll();
\IPS\Output\Cache::i()->clearAll();
\IPS\Member::clearCreateMenu();
\IPS\Session::i()->log( 'acplog__support_tool_caches_cleared' );
if( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->json( 'OK' );
}
else
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=support&controller=support' ) );
}
}
/**
@@ -998,6 +1142,11 @@ class _support extends \IPS\Dispatcher\Controller
$disabledAppNames[ $app->directory ] = $app->_title;
}
if ( \count( $disabledApps ) )
{
\IPS\Session::i()->log( 'acplog__support_tool_apps_disabled' );
}
/* Look Plugins */
foreach ( $this->_thirdPartyPlugins() as $plugin )
{
@@ -1006,6 +1155,11 @@ class _support extends \IPS\Dispatcher\Controller
$disabledPlugins[] = $plugin->id;
$disabledPluginNames[ $plugin->id ] = $plugin->_title;
}
if ( \count( $disabledPlugins ) )
{
\IPS\Session::i()->log( 'acplog__support_tool_plugins_disabled' );
}
if( \count( $this->_thirdPartyApps() ) )
{
@@ -1035,6 +1189,11 @@ class _support extends \IPS\Dispatcher\Controller
$restoredDefaultTheme = TRUE;
}
if ( $restoredDefaultTheme )
{
\IPS\Session::i()->log( 'acplog__support_tool_theme_restored' );
}
/* Do we need to revert the editor? */
if ( $this->_thirdPartyEditor() )
{
@@ -1048,6 +1207,11 @@ class _support extends \IPS\Dispatcher\Controller
$restoredEditor = TRUE;
}
if ( $restoredEditor )
{
\IPS\Session::i()->log( 'acplog__support_tool_editor_restored' );
}
/* Do we need to disable any thid party ads? */
foreach ( $this->_thirdPartyAds() as $ad )
{
@@ -1057,6 +1221,11 @@ class _support extends \IPS\Dispatcher\Controller
$disabledAds[] = $ad->id;
}
if ( \count( $disabledAds ) )
{
\IPS\Session::i()->log( 'acplog__support_tool_ads_disabled' );
}
/* Clear cache */
\IPS\Data\Cache::i()->clearAll();
@@ -1092,6 +1261,7 @@ class _support extends \IPS\Dispatcher\Controller
try
{
\IPS\Theme::load( $_SESSION['thirdParty']['deleteTheme'] )->delete();
\IPS\Session::i()->log( 'acplog__support_tool_theme_deleted' );
}
catch ( \Exception $e ) {}
@@ -1113,6 +1283,7 @@ class _support extends \IPS\Dispatcher\Controller
if( $_SESSION['thirdParty']['enableApps'] )
{
\IPS\Application::postToggleEnable();
\IPS\Session::i()->log( 'acplog__support_tool_apps_enabled' );
}
unset( $_SESSION['thirdParty']['enableApps'] );
@@ -1133,6 +1304,7 @@ class _support extends \IPS\Dispatcher\Controller
if( $_SESSION['thirdParty']['enablePlugins'] )
{
\IPS\Plugin::postToggleEnable( TRUE );
\IPS\Session::i()->log( 'acplog__support_tool_plugins_enabled' );
}
unset( $_SESSION['thirdParty']['enablePlugins'] );
@@ -1145,6 +1317,8 @@ class _support extends \IPS\Dispatcher\Controller
\IPS\Settings::i()->changeValues( array( 'ckeditor_extraPlugins' => $editorConfiguration['extraPlugins'], 'ckeditor_toolbars' => $editorConfiguration['toolbars'] ) );
\IPS\Session::i()->log( 'acplog__support_tool_editor_customized' );
unset( \IPS\Data\Store::i()->editorConfigurationToRestore );
unset( $_SESSION['thirdParty']['restoreEditor'] );
}
@@ -1162,6 +1336,11 @@ class _support extends \IPS\Dispatcher\Controller
}
catch ( \Exception $e ) {}
}
if ( $_SESSION['thirdParty']['enableAds'] )
{
\IPS\Session::i()->log( 'acplog__support_tool_ads_enabled' );
}
unset( $_SESSION['thirdParty']['enableAds'] );
}
@@ -1207,10 +1386,6 @@ class _support extends \IPS\Dispatcher\Controller
'details' => !\IPS\CIC ? \IPS\Member::loggedIn()->language()->addToStack( 'acp_version_number_raw', FALSE, array( 'sprintf' => array( \IPS\Db::i()->server_info ) ) ) : NULL
);
$blocks['thirdparty'] = array(
'title' => \IPS\Member::loggedIn()->language()->addToStack('health__thirdparty_title')
);
if( !\IPS\CIC )
{
$blocks['caching'] = array(
@@ -1374,11 +1549,15 @@ class _support extends \IPS\Dispatcher\Controller
}
$results = array();
$where = array( array( "{$date}>?", 0 ) );
$where = array();
if ( $chart->start )
{
$where[] = array( "{$date}>?", $chart->start->getTimestamp() );
}
else
{
$where[] = array( "{$date}>?", 0 );
}
if ( $chart->end )
{
$where[] = array( "{$date}<?", $chart->end->getTimestamp() );
@@ -1391,11 +1570,11 @@ class _support extends \IPS\Dispatcher\Controller
$fromUnixTime = "CONVERT_TZ( {$fromUnixTime}, @@session.time_zone, '" . \IPS\Db::i()->escape_string( \IPS\Member::loggedIn()->timezone ) . "' )";
}
$stmt = \IPS\Db::i()->select( "COUNT(*) as total, DATE_FORMAT( {$fromUnixTime}, '{$timescale}' ) AS time", $table, $where, 'time ASC', NULL, array( 'time' ) );
$stmt = \IPS\Db::i()->select( "COUNT(*) as total, DATE_FORMAT( {$fromUnixTime}, '{$timescale}' ) AS ctime", $table, $where, 'ctime ASC', NULL, array( 'ctime' ) );
foreach( $stmt as $row )
{
$results[ $row['time'] ] = $row['total'];
$results[ $row['ctime'] ] = $row['total'];
}
return $results;
@@ -1649,4 +1828,101 @@ class _support extends \IPS\Dispatcher\Controller
{
return \IPS\Db::i()->select( '*','core_advertisements', array( 'ad_active=?', 1 ) );
}
/**
* Create Admin
*
* @return void
*/
public function admin()
{
if ( \IPS\Login\Handler::findMethod( 'IPS\Login\Handler\Standard' ) )
{
$password = '';
$length = rand( 8, 15 );
for ( $i = 0; $i < $length; $i++ )
{
do {
$key = rand( 33, 126 );
} while ( \in_array( $key, array( 34, 39, 60, 62, 92 ) ) );
$password .= \chr( $key );
}
$supportAccount = \IPS\Member::load( 'ipstempadmin@invisionpower.com', 'email' );
if ( !$supportAccount->member_id )
{
$supportAccount = \IPS\Member::load( 'nobody@invisionpower.com', 'email' );
}
if ( !$supportAccount->member_id )
{
$name = 'IPS Temp Admin';
$_supportAccount = \IPS\Member::load( $name, 'name' );
if ( $_supportAccount->member_id )
{
$number = 2;
while ( $_supportAccount->member_id )
{
$name = "IPS Temp Admin {$number}";
$_supportAccount = \IPS\Member::load( $name, 'name' );
$number++;
}
}
$supportAccount = new \IPS\Member;
$supportAccount->name = $name;
$supportAccount->member_group_id = \IPS\Settings::i()->admin_group;
}
/* Always update the email in case we found the old "nobody" support account. */
$supportAccount->email = 'ipstempadmin@invisionpower.com';
/* Set english language to the admin account / create new english language if needed */
$locales = array( 'en_US', 'en_US.UTF-8', 'en_US.UTF8', 'en_US.utf8', 'english' );
try
{
$existingEnglishLangPack = \IPS\Db::i()->select( 'lang_id', 'core_sys_lang', array( \IPS\Db::i()->in( 'lang_short', $locales ) ) )->first();
$supportAccount->language = $existingEnglishLangPack;
$supportAccount->acp_language = $existingEnglishLangPack;
}
catch ( \UnderflowException $e )
{
/* Install the default language */
$locale = 'en_US';
foreach ( $locales as $k => $localeCode )
{
try
{
\IPS\Lang::validateLocale( $localeCode );
$locale = $localeCode;
break;
}
catch ( \InvalidArgumentException $e ){}
}
$insertId = \IPS\Db::i()->insert( 'core_sys_lang', array(
'lang_short' => $locale,
'lang_title' => "Default ACP English",
'lang_enabled' => 0,
) );
$supportAccount->language = $insertId;
$supportAccount->acp_language = $insertId;
/* Initialize Background Task to insert the language strings */
foreach ( \IPS\Application::applications() as $key => $app )
{
\IPS\Task::queue( 'core', 'InstallLanguage', array( 'application' => $key, 'language_id' => $insertId ), 1 );
}
}
$supportAccount->members_bitoptions['is_support_account'] = TRUE;
$supportAccount->setLocalPassword( $password );
$supportAccount->save();
\IPS\core\AdminNotification::send( 'core', 'ConfigurationError', "supportAdmin-{$supportAccount->member_id}", TRUE, NULL, TRUE );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'administrator_account' );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'support' )->admin( $supportAccount->name, $supportAccount->email, $password );
}
}
}
@@ -174,6 +174,19 @@ class _systemLogs extends \IPS\Dispatcher\Controller
\IPS\Output::i()->breadcrumb[] = array( \IPS\Http\Url::internal( "app=core&module=support&controller=systemLogs" ), \IPS\Member::loggedIn()->language()->addToStack('r__system_logs') );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'system' )->systemLogView( $log );
}
/**
* Delete all logs
*
* @return void
*/
protected function deleteAll()
{
/* Delete */
\IPS\Db::i()->query( "TRUNCATE core_log" );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=support&controller=systemLogs' ), 'deleted' );
}
/**
* Delete a log
@@ -26,17 +26,6 @@ class _utf8mb4 extends \IPS\Dispatcher\Controller
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'sql_toolbox' );
parent::execute();
}
/**
* Bootstrap
@@ -109,7 +109,7 @@ class _upgrade extends \IPS\Dispatcher\Controller
$coreApp->save();
/* Check if we should allow the upgrade to proceed */
if( \IPS\CIC AND \IPS\Cicloud\isManaged() AND isset( $response['blockms'] ) AND $response['blockms'] )
if( \IPS\CIC AND \IPS\IPS::isManaged() )
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('system')->upgradeManagedContactUs( \IPS\Cicloud\managedSupportEmail() );
\IPS\Dispatcher::i()->finish();
@@ -187,6 +187,34 @@ class _upgrade extends \IPS\Dispatcher\Controller
/* Do a database check */
if( !\IPS\IPS_ALPHA_BUILD )
{
$md5CheckVersion = NULL;
/* If the files on disk are already newer, set version for MD5 check (i.e. if the files were already applied, but the upgrade hasn't happened) */
if( (int) \IPS\Application::getAvailableVersion('core') > \IPS\Application::load('core')->long_version )
{
$md5CheckVersion = (int) \IPS\Application::getAvailableVersion('core');
}
/* Check our files aren't modified */
if ( !\IPS\Request::i()->skip_md5_check )
{
try
{
$files = \IPS\Application::md5Check( $md5CheckVersion );
if ( \count( $files ) )
{
return \IPS\Theme::i()->getTemplate('system')->upgradeDeltaMd5( $values['version'], $files );
}
}
catch ( \Exception $e ) {}
}
/* If we set a custom md5 check version and it passed, proceed to full upgrader */
if( $md5CheckVersion )
{
\IPS\Output::i()->redirect( 'upgrade/?autologin=1' );
}
/* Run the database check for each IPS app */
foreach ( \IPS\Application::enabledApplications() as $app )
{
if( \in_array( $app->directory, \IPS\IPS::$ipsApps ) )
@@ -197,24 +225,10 @@ class _upgrade extends \IPS\Dispatcher\Controller
}
}
}
/* Check our files aren't modified */
if ( !\IPS\Request::i()->skip_md5_check )
{
try
{
$files = \IPS\Application::md5Check();
if ( \count( $files ) )
{
return \IPS\Theme::i()->getTemplate('system')->upgradeDeltaMd5( $values['version'], $files );
}
}
catch ( \Exception $e ) {}
}
}
/* Get details */
$data = array( 'version' => $values['version'], 'oldVersion' => \intval( \IPS\Application::getAvailableVersion('core') ), 'changes' => array() );
$data = array( 'version' => $values['version'], 'oldVersion' => \IPS\Application::load('core')->long_version, 'changes' => array() );
$data['info'] = $this->_changeDetails( $data['oldVersion'], $values['version'] );
foreach ( $data['info']['steps'] as $k => $v )
{
@@ -236,9 +250,12 @@ class _upgrade extends \IPS\Dispatcher\Controller
foreach ( \IPS\Db::i()->select( array( 'template_id', 'template_set_id', 'template_app', 'template_location', 'template_group', 'template_name' ), 'core_theme_templates', array( 'template_set_id>0' ) ) as $modifiedTemplate )
{
$templateKey = "{$modifiedTemplate['template_location']}/{$modifiedTemplate['template_group']}/{$modifiedTemplate['template_name']}";
if ( \in_array( $modifiedTemplate['template_set_id'], $validThemeIds ) and \in_array( $templateKey, $data['changes']['theme'][ $modifiedTemplate['template_app'] ]['html']['edited'] ) )
if ( isset( $data['changes']['theme'][ $modifiedTemplate['template_app'] ] ) )
{
$conflicts[ $modifiedTemplate['template_set_id'] ]['html'][ $modifiedTemplate['template_app'] . '/' . $templateKey ] = $modifiedTemplate['template_id'];
if ( \in_array( $modifiedTemplate['template_set_id'], $validThemeIds ) and \in_array( $templateKey, $data['changes']['theme'][ $modifiedTemplate['template_app'] ]['html']['edited'] ) )
{
$conflicts[ $modifiedTemplate['template_set_id'] ]['html'][ $modifiedTemplate['template_app'] . '/' . $templateKey ] = $modifiedTemplate['template_id'];
}
}
}
@@ -246,9 +263,12 @@ class _upgrade extends \IPS\Dispatcher\Controller
foreach ( \IPS\Db::i()->select( array( 'css_id', 'css_set_id', 'css_app', 'css_location', 'css_path', 'css_name' ), 'core_theme_css', array( 'css_set_id>0' ) ) as $modifiedCss )
{
$cssKey = "{$modifiedCss['css_location']}/" . ( $modifiedCss['css_path'] ? "{$modifiedCss['css_path']}/" : '' ) . $modifiedCss['css_name'];
if ( \in_array( $modifiedCss['css_set_id'], $validThemeIds ) and \in_array( $cssKey, $data['changes']['theme'][ $modifiedCss['css_app'] ]['css']['edited'] ) )
if ( isset( $data['changes']['theme'][ $modifiedCss['css_app'] ] ) )
{
$conflicts[ $modifiedCss['css_set_id'] ]['css'][ $modifiedCss['css_app'] . '/' . $cssKey ] = $modifiedCss['css_set_id'];
if ( \in_array( $modifiedCss['css_set_id'], $validThemeIds ) and \in_array( $cssKey, $data['changes']['theme'][ $modifiedCss['css_app'] ]['css']['edited'] ) )
{
$conflicts[ $modifiedCss['css_set_id'] ]['css'][ $modifiedCss['css_app'] . '/' . $cssKey ] = $modifiedCss['css_set_id'];
}
}
}
@@ -469,6 +489,16 @@ class _upgrade extends \IPS\Dispatcher\Controller
$form->add( new \IPS\Helpers\Form\Email( 'ips_email_address', NULL ) );
$form->add( new \IPS\Helpers\Form\Password( 'ips_password', NULL ) );
if ( !\IPS\CIC )
{
$form->add( new \IPS\Helpers\Form\YesNo( 'upgrade_confirm_backup', FALSE, TRUE, array(), function( $val ) {
if ( !$val )
{
throw new \DomainException( 'form_required' );
}
} ) );
}
/* Handle submissions */
if ( $values = $form->values() )
{
@@ -656,7 +686,7 @@ class _upgrade extends \IPS\Dispatcher\Controller
\IPS\Settings::i()->changeValues( array( 'upgrade_ftp_details' => $encrypted->tag() ) );
}
$data['ftpDetails'] = $values['delta_upgrade_ftp_details'];
$data['ftpDetails'] = 1;
return $data;
}
@@ -773,6 +803,7 @@ class _upgrade extends \IPS\Dispatcher\Controller
elseif ( $response->httpResponseCode == 206 )
{
$totalFileSize = \intval( mb_substr( $response->httpHeaders['Content-Range'], mb_strpos( $response->httpHeaders['Content-Range'], '/' ) + 1 ) );
$fh = \fopen( $mrData['tmpFileName'], 'a' );
\fwrite( $fh, (string) $response );
\fclose( $fh );
@@ -903,12 +934,12 @@ class _upgrade extends \IPS\Dispatcher\Controller
return;
}
}
}
$perStepPercentage = ( 100 / \count( $steps ) );
}
if ( array_key_exists( $mrData['step'], $steps ) )
if ( \count( $steps ) AND array_key_exists( $mrData['step'], $steps ) )
{
$perStepPercentage = ( 100 / \count( $steps ) );
$step = $steps[ $mrData['step'] ];
$percentage = $perStepPercentage * \intval( $mrData['step'] );
$stepData = isset( $mrData[ $step ] ) ? $mrData[ $step ] : array();