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();
@@ -22,6 +22,18 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
*/
class _directory extends \IPS\Dispatcher\Controller
{
/**
* @brief These properties are used to specify datalayer context properties.
*
* @example array(
'community_area' => array( 'value' => 'search', 'odkUpdate' => 'true' )
* )
*/
public static $dataLayerContext = array(
'community_area' => [ 'value' => 'clubs', 'odkUpdate' => 'true']
);
/**
* Execute
*
@@ -27,6 +27,14 @@ class _page extends \IPS\Dispatcher\Controller
* @brief Page
*/
protected $page;
/**
* @brief These properties are used to specify datalayer context properties.
*
*/
public static $dataLayerContext = array(
'community_area' => [ 'value' => 'clubs', 'odkUpdate' => 'true']
);
/**
* Execute
+15 -17
View File
@@ -31,6 +31,14 @@ class _view extends \IPS\Helpers\CoverPhoto\Controller
* @brief The logged in user's status
*/
protected $memberStatus;
/**
* @brief These properties are used to specify datalayer context properties.
*
*/
public static $dataLayerContext = array(
'community_area' => [ 'value' => 'clubs', 'odkUpdate' => 'true']
);
/**
* Execute
@@ -135,15 +143,6 @@ class _view extends \IPS\Helpers\CoverPhoto\Controller
{
\IPS\Output::i()->redirect( (string) $firstTab[ $key ]['href'] );
}
/* If this is a closed club, and we don't have permission to read it, show the member page instead */
if ( $this->club->type == \IPS\Member\Club::TYPE_CLOSED && !$this->club->canRead() )
{
$this->members();
return;
}
else
{
if( $key == 'club_members' )
{
$this->members();
@@ -153,7 +152,6 @@ class _view extends \IPS\Helpers\CoverPhoto\Controller
$this->overview();
}
return;
}
}
/**
@@ -1124,7 +1122,6 @@ class _view extends \IPS\Helpers\CoverPhoto\Controller
{
\IPS\Output::i()->redirect( $this->club->url()->setQueryString( 'rulesAcknowledged', 1 ), 'accepted' );
}
}
$this->club->acknowledgeRules( \IPS\Member::loggedIn() );
@@ -1553,7 +1550,7 @@ class _view extends \IPS\Helpers\CoverPhoto\Controller
/* Build Form */
$form = new \IPS\Helpers\Form;
$form->class = 'ipsForm_vertical';
$node->clubForm( $form );
$node->clubForm( $form, $this->club );
/* Handle submissions */
if ( $values = $form->values() )
@@ -1584,13 +1581,14 @@ class _view extends \IPS\Helpers\CoverPhoto\Controller
/* Load */
$class = \IPS\Request::i()->type;
if( !$class instanceof \IPS\Node\Model )
{
throw new \Exception;
}
try
{
if ( !\in_array( 'IPS\Node\Model', class_parents( $class ) ) )
{
throw new \Exception;
}
$node = $class::load( (int) \IPS\Request::i()->node );
$club = $node->club();
if ( !$club or $club->id !== $this->club->id )
@@ -54,6 +54,11 @@ class _popular extends \IPS\Dispatcher\Controller
$tabs[] = $addTab;
}
}
if ( \IPS\Application::appIsEnabled('cloud') and \IPS\cloud\Application::featureIsEnabled('trending') )
{
$tabs[] = 'trending';
}
$activeTab = ( isset( \IPS\Request::i()->tab ) and \in_array(\IPS\Request::i()->tab, $tabs ) ) ? \IPS\Request::i()->tab : \IPS\Settings::i()->reputation_leaderboard_default_tab;
@@ -63,7 +68,13 @@ class _popular extends \IPS\Dispatcher\Controller
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'leaderboard_tabs_' . $activeTab );
$content = $this->$activeTab();
/* Data Layer Context Property */
if ( \IPS\Settings::i()->core_datalayer_enabled AND ! \IPS\Request::i()->isAjax() )
{
\IPS\core\DataLayer::i()->addContextProperty( 'community_area', 'leaderboard', true );
}
if ( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate( 'global', 'core' )->blankTemplate( $content ), 200, 'text/html' );
@@ -148,12 +159,12 @@ class _popular extends \IPS\Dispatcher\Controller
if ( \IPS\Content\Reaction::isLikeMode() )
{
\IPS\Member::loggedIn()->language()->words['leader_rep_total'] = \IPS\Member::loggedIn()->language()->addToStack( 'leader_rep_total_likes' );
\IPS\Member::loggedIn()->language()->words['leaderboard_history__desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'leaderboard_history_desc', NULL, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( 'leaderboard_history_likes') ) ) );
\IPS\Member::loggedIn()->language()->words['leaderboard_history__desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'leaderboard_history_desc', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( 'leaderboard_history_likes') ) ) );
}
else
{
\IPS\Member::loggedIn()->language()->words['leader_rep_total'] = \IPS\Member::loggedIn()->language()->addToStack( 'leader_rep_total_rep' );
\IPS\Member::loggedIn()->language()->words['leaderboard_history__desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'leaderboard_history_desc', NULL, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( 'leaderboard_history_rep') ) ) );
\IPS\Member::loggedIn()->language()->words['leaderboard_history__desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'leaderboard_history_desc', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( 'leaderboard_history_rep') ) ) );
}
/* Parsers */
@@ -179,7 +190,7 @@ class _popular extends \IPS\Dispatcher\Controller
{
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'title_with_page_number', FALSE, array( 'sprintf' => array( \IPS\Output::i()->title, $table->page ) ) );
}
return (string) $table;
}
}
@@ -287,8 +298,8 @@ class _popular extends \IPS\Dispatcher\Controller
$form = new \IPS\Helpers\Form( 'popular_date', 'continue' );
$form->class = 'ipsForm_vertical';
$customStart = isset( \IPS\Request::i()->custom_date_start ) ? \IPS\Request::i()->custom_date_start : NULL;
$customEnd = isset( \IPS\Request::i()->custom_date_end ) ? \IPS\Request::i()->custom_date_end : NULL;
$customStart = ( isset( \IPS\Request::i()->custom_date_start ) and \is_numeric( \IPS\Request::i()->custom_date_start ) ) ? (int) \IPS\Request::i()->custom_date_start : NULL;
$customEnd = ( isset( \IPS\Request::i()->custom_date_end ) and \is_numeric( \IPS\Request::i()->custom_date_end ) ) ? (int) \IPS\Request::i()->custom_date_end : NULL;
$form->add( new \IPS\Helpers\Form\DateRange( 'custom_date', array( 'start' => $customStart, 'end' => $customEnd ), FALSE, array( 'start' => array( 'min' => $dates[ 'oldest' ], 'time' => false ) ) ) );
@@ -327,7 +338,7 @@ class _popular extends \IPS\Dispatcher\Controller
if ( isset( \IPS\Request::i()->in ) and isset( $areas[ \IPS\Request::i()->in ] ) )
{
$appWhere = " AND rep_class='" . \IPS\Db::i()->escape_string( $areas[ \IPS\Request::i()->in ][ 0 ] ) . "'";
$descApp = \IPS\Member::loggedIn()->language()->addToStack( 'leaderboard_in_app', NULL, array( 'sprintf' => array( $areas[ \IPS\Request::i()->in ][ 1 ] ) ) );
$descApp = \IPS\Member::loggedIn()->language()->addToStack( 'leaderboard_in_app', FALSE, array( 'sprintf' => array( $areas[ \IPS\Request::i()->in ][ 1 ] ) ) );
$customApp = TRUE;
}
else
@@ -50,7 +50,7 @@ class _rss extends \IPS\Dispatcher\Controller
$member = \IPS\Member::load( \IPS\Request::i()->member_id );
/* Make sure we have an actual member, and that the key matches. If it doesn't, we can bubble up and see if the feed works for guests, and just use that */
if ( $member->member_id AND \IPS\Login::compareHashes( (string) \IPS\Request::i()->key, md5( $member->member_id . $member->email . $member->member_pass_hash . $member->member_pass_salt ) ) )
if ( $member->member_id AND \IPS\Login::compareHashes( $member->getUniqueMemberHash(), (string) \IPS\Request::i()->key ) )
{
/* Make sure we have access to this feed. */
if ( $feed->groups == '*' OR $member->inGroup( $feed->groups ) )
@@ -22,6 +22,14 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
*/
class _streams extends \IPS\Dispatcher\Controller
{
/**
* @brief These properties are used to specify datalayer context properties.
*
*/
public static $dataLayerContext = array(
'community_area' => [ 'value' => 'activity_stream', 'odkUpdate' => 'true']
);
/**
* Execute
*
@@ -73,16 +81,26 @@ class _streams extends \IPS\Dispatcher\Controller
/* RSS validate? */
$member = NULL;
if ( isset( \IPS\Request::i()->rss ) )
{
$member = \IPS\Member::load( \IPS\Request::i()->member );
if ( !\IPS\Login::compareHashes( md5( ( $member->members_pass_hash ?: $member->email ) . $member->members_pass_salt ), (string) \IPS\Request::i()->key ) )
if ( isset( \IPS\Request::i()->member ) AND isset( \IPS\Request::i()->key ) )
{
$member = NULL;
$member = \IPS\Member::load( \IPS\Request::i()->member );
if ( !$member->member_id OR !\IPS\Login::compareHashes( $member->getUniqueMemberHash(), (string) \IPS\Request::i()->key ) )
{
$member = NULL;
}
/* If we do not have a specific member, and this member is not allowed to view the site, throw an error as they do not have permission */
if ( ! $member and ! \IPS\Member::loggedIn()->group['g_view_board'] )
{
\IPS\Output::i()->error( 'stream_no_permission', '2C280/G', 403, '' );
}
}
}
$form = NULL;
/* Viewing a particular stream? */
if ( isset( \IPS\Request::i()->id ) )
@@ -205,7 +223,7 @@ class _streams extends \IPS\Dispatcher\Controller
/* but only if we haven't submitted the form on this request */
if( !\IPS\Request::i()->stream_submitted )
{
$streamFields = array( 'include_comments', 'classes', 'ownership', 'custom_members', 'read', 'follow', 'followed_types', 'date_type', 'date_start', 'date_end', 'date_relative_days', 'sort', 'tags' );
$streamFields = array( 'include_comments', 'classes', 'ownership', 'custom_members', 'read', 'follow', 'followed_types', 'date_type', 'date_start', 'date_end', 'date_relative_days', 'sort', 'tags', 'solved' );
/* Clubs enabled? */
if( \IPS\Settings::i()->clubs )
@@ -296,10 +314,40 @@ class _streams extends \IPS\Dispatcher\Controller
$form = $this->_buildForm( $stream );
}
}
/* Condensed or expanded? */
$view = 'expanded';
$streamID = ( \IPS\Request::i()->id ) ? \IPS\Request::i()->id : 'all';
if( isset( \IPS\Request::i()->view ) AND \IPS\Login::compareHashes( (string) \IPS\Session::i()->csrfKey, (string) \IPS\Request::i()->csrfKey ) )
{
$view = \IPS\Request::i()->view;
\IPS\Request::i()->setCookie( 'stream_view_' . $streamID, \IPS\Request::i()->view, \IPS\DateTime::create()->add( new \DateInterval( 'P1Y' ) ) );
if( !\IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->redirect( $stream->url() );
}
}
elseif ( isset( \IPS\Request::i()->cookie['stream_view_' . $streamID] ) )
{
$view = \IPS\Request::i()->cookie['stream_view_' . $streamID];
}
else
{
$view = $stream->default_view;
}
/* Ensure correct params are set for view mode */
if ( $view === 'condensed' )
{
$stream->include_comments = FALSE;
}
/* Build the query */
$query = $stream->query( $member );
/* Set page or the before/after date */
$currentPage = 1;
if ( isset( \IPS\Request::i()->page ) AND \intval( \IPS\Request::i()->page ) > 0 )
@@ -308,8 +356,8 @@ class _streams extends \IPS\Dispatcher\Controller
$query->setPage( $currentPage );
}
$before = isset( \IPS\Request::i()->before ) ? \IPS\Request::i()->before : null;
$after = isset( \IPS\Request::i()->after ) ? \IPS\Request::i()->after : null;
$before = ( isset( \IPS\Request::i()->before ) and \is_numeric( \IPS\Request::i()->before ) ) ? (int) \IPS\Request::i()->before : null;
$after = ( isset( \IPS\Request::i()->after ) and \is_numeric( \IPS\Request::i()->after ) ) ? (int) \IPS\Request::i()->after : null;
/* If we sort by oldest, then we need to switch these values */
if ( $stream->sort == 'oldest' )
@@ -380,30 +428,6 @@ class _streams extends \IPS\Dispatcher\Controller
$results = $results->addExtraItems( $extra, NULL, ( \IPS\Request::i()->isAjax() and isset( \IPS\Request::i()->after ) ) ? \IPS\DateTime::ts( \IPS\Request::i()->after ) : NULL, ( \IPS\Request::i()->isAjax() and isset( \IPS\Request::i()->before ) ) ? \IPS\DateTime::ts( \IPS\Request::i()->before ) : NULL );
}
}
/* Condensed or expanded? */
$view = 'expanded';
$streamID = ( \IPS\Request::i()->id ) ? \IPS\Request::i()->id : 'all';
if( isset( \IPS\Request::i()->view ) AND \IPS\Login::compareHashes( (string) \IPS\Session::i()->csrfKey, (string) \IPS\Request::i()->csrfKey ) )
{
$view = \IPS\Request::i()->view;
\IPS\Request::i()->setCookie( 'stream_view_' . $streamID, \IPS\Request::i()->view, \IPS\DateTime::create()->add( new \DateInterval( 'P1Y' ) ) );
if( !\IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->redirect( $stream->url() );
}
}
elseif ( isset( \IPS\Request::i()->cookie['stream_view_' . $streamID] ) )
{
$view = \IPS\Request::i()->cookie['stream_view_' . $streamID];
}
else
{
$view = $stream->default_view;
}
/* If this is an AJAX request, just show the results */
if ( \IPS\Request::i()->isAjax() )
@@ -453,7 +477,7 @@ class _streams extends \IPS\Dispatcher\Controller
$rssLink = \IPS\Http\Url::internal( "app=core&module=discover&controller=streams&id={$stream->id}", 'front', 'discover_rss' );
if ( \IPS\Member::loggedIn()->member_id )
{
$rssLink = $rssLink->setQueryString( 'member', \IPS\Member::loggedIn()->member_id )->setQueryString( 'key', md5( ( \IPS\Member::loggedIn()->members_pass_hash ?: \IPS\Member::loggedIn()->email ) . \IPS\Member::loggedIn()->members_pass_salt ) );
$rssLink = $rssLink->setQueryString( 'member', \IPS\Member::loggedIn()->member_id )->setQueryString( 'key', \IPS\Member::loggedIn()->getUniqueMemberHash() );
}
}
else
@@ -634,7 +658,7 @@ class _streams extends \IPS\Dispatcher\Controller
$url = $stream ? $stream->baseUrl->setQueryString( 'className', $class ) : \IPS\Request::i()->url()->setQueryString( 'className', $class );
$containerClass = $class::$containerNodeClass;
$field = new \IPS\Helpers\Form\Node( 'stream_containers_' . $class::$title, isset( $currentContainers[ $class ] ) ? $currentContainers[ $class ] : array(), NULL, array( 'url' => $url, 'class' => $class::$containerNodeClass, 'multiple' => TRUE, 'permissionCheck' => $containerClass::searchableNodesPermission(), 'clubs' => ( \IPS\Settings::i()->clubs AND \IPS\IPS::classUsesTrait( $containerClass, 'IPS\Content\ClubContainer' ) ) ), NULL, NULL, NULL, 'stream_containers_' . $class::$title );
$field->label = \IPS\Member::loggedIn()->language()->addToStack( 'stream_narrow_by_container_label', NULL, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( $containerClass::$nodeTitle ) ) ) );
$field->label = \IPS\Member::loggedIn()->language()->addToStack( 'stream_narrow_by_container_label', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( $containerClass::$nodeTitle ) ) ) );
return \IPS\Output::i()->json( array( 'node' => \IPS\Theme::i()->getTemplate('streams')->filterFormContentTypeContent( $field, $class, \IPS\Request::i()->key ) ) );
}
@@ -761,7 +785,7 @@ class _streams extends \IPS\Dispatcher\Controller
{
$formattedValues = $stream->formatFormValues( $values );
foreach ( array( 'include_comments', 'classes', 'containers', 'clubs', 'ownership', 'custom_members', 'read', 'follow', 'followed_types', 'date_type', 'date_start', 'date_end', 'date_relative_days', 'sort', 'tags' ) as $k )
foreach ( array( 'include_comments', 'classes', 'containers', 'clubs', 'ownership', 'custom_members', 'read', 'follow', 'followed_types', 'date_type', 'date_start', 'date_end', 'date_relative_days', 'sort', 'tags', 'solved' ) as $k )
{
$requestKey = 'stream_' . $k;
@@ -802,6 +826,172 @@ class _streams extends \IPS\Dispatcher\Controller
/* Display */
return $form->customTemplate( array( \IPS\Theme::i()->getTemplate( 'streams', 'core' ), $stream->id ? 'filterInlineForm' : 'filterCreateForm' ) );
}
/**
*
*/
protected function subscribe()
{
/* Get it */
try
{
$stream = \IPS\core\Stream::load( \IPS\Request::i()->id );
}
catch ( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2C280/C', 404, '' );
}
if( !$stream->canSubscribe() )
{
\IPS\Output::i()->error( 'stream_no_permission', '2C280/D', 403, '' );
}
$form = new \IPS\Helpers\Form('subscribe', 'stream_subscribe_button');
$form->class = 'ipsForm_vertical';
$form->add( new \IPS\Helpers\Form\Radio('stream_subscription_frequency', NULL , TRUE, ['options' => ['daily' => 'stream_daily', 'weekly' => 'stream_weekly']] ));
if( $values = $form->values() )
{
$streamSub = new \IPS\core\Stream\Subscription();
$streamSub->member_id = \IPS\Member::loggedIn()->member_id;
$streamSub->stream_id = $stream->id;
$streamSub->frequency = $values['stream_subscription_frequency'];
/* Set the fake last sent time to get the next run populated correct */
$streamSub->sent = $values['stream_subscription_frequency'] == 'daily' ? \IPS\DateTime::create()->sub( new \DateInterval( 'P1D' ) )->getTimestamp() : \IPS\DateTime::create()->sub( new \DateInterval( 'P1W' ) )->getTimestamp();
$streamSub->added = time();
$streamSub->save();
/* Enable the task */
$taskKey = $values['stream_subscription_frequency'] == 'daily' ? 'dailyStreamSubscriptions' : 'weeklyStreamSubscriptions';
\IPS\Db::i()->update( 'core_tasks', array( 'enabled' => 1 ), array( '`key`=?', $taskKey ) );
\IPS\Output::i()->redirect( $stream->url(), 'subscribed' );
}
\IPS\Output::i()->output = $form->customTemplate( array( \IPS\Theme::i()->getTemplate( 'forms', 'core' ), 'popupTemplate' ) );
if ( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->sendOutput( \IPS\Output::i()->output );
}
else
{
\IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate( 'global', 'core' )->globalTemplate( \IPS\Output::i()->title, \IPS\Output::i()->output, array( 'app' => \IPS\Dispatcher::i()->application->directory, 'module' => \IPS\Dispatcher::i()->module->key, 'controller' => \IPS\Dispatcher::i()->controller ) ), 200, 'text/html' );
}
}
/**
* Unsubscribe from a stream
*/
protected function unsubscribe()
{
/* Get it */
try
{
$stream = \IPS\core\Stream::load( \IPS\Request::i()->id );
}
catch ( \OutOfRangeException|\UnderflowException $e )
{
\IPS\Output::i()->error( 'node_error', '2C280/F', 404, '' );
}
\IPS\Request::i()->confirmedDelete( 'stream_unsubscribe', 'confirm_stream_unsubscribe', 'stream_unsubscribe_button');
if ( $streamSubscription = \IPS\core\Stream\Subscription::loadByStreamAndMember( $stream ) )
{
$streamSubscription->delete();
}
else
{
\IPS\Output::i()->error( 'stream_no_permission', '2C280/E', 403, '' );
}
\IPS\Output::i()->redirect( $stream->url(), 'unsubscribed' );
}
/**
* Unsubscribe from a stream or from all streams from email
* If we're logged in, we can send them right to the normal form.
* Otherwise, they get a special guest page using the gkey as an authentication key.
*
* @return void
*/
protected function unSubscribeFromEmail()
{
/* Logged in? */
if ( \IPS\Member::loggedIn()->member_id )
{
/* Go to the normal page */
\IPS\Output::i()->redirect( $this->url->setQueryString('do','unsubscribe')->setQueryString('id', \IPS\Request::i()->id));
}
if ( !empty( \IPS\Request::i()->gkey ) )
{
try
{
$current = \IPS\Db::i()->select( '*', 'core_stream_subscriptions', [ 'id=?', \IPS\Request::i()->id ])->first();
}
catch ( \UnderFlowException $e )
{
\IPS\Output::i()->error('node_error', '2C280/E');
}
$member = \IPS\Member::load( $current['member_id'] );
if ( md5( $member->email . ';' . $member->ip_address . ';' . $member->joined->getTimestamp() ) != \IPS\Request::i()->gkey )
{
\IPS\Output::i()->error( 'stream_no_permission', '2C280/E', 403, '' );
}
$form = new \IPS\Helpers\Form( '', 'update_stream_subscriptions' );
$form->class = 'ipsForm_vertical';
if ( $streams = \IPS\core\Stream\Subscription::getSubscribedStreams( $member ) AND $count = \count( $streams ) AND $count == 1 )
{
$title = \IPS\core\Stream\Subscription::constructFromData($current)->stream->_title;
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'streamsubscription_guest_thing', FALSE, array('sprintf' => array($title)) );
$form->add( new \IPS\Helpers\Form\Checkbox( 'guest_unsubscribe_single', 'single', FALSE, array('disabled' => true) ) );
\IPS\Member::loggedIn()->language()->words['guest_unsubscribe_single'] = \IPS\Member::loggedIn()->language()->addToStack( 'stream_subscription_unsubscribe_thing', FALSE, array('sprintf' => array($title)) );
}
else
{
$title = \IPS\Member::loggedIn()->language()->addToStack( 'stream_subscriptions');
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'streamsubscription_guest_all' );
$form->add( new \IPS\Helpers\Form\Radio( 'guest_unsubscribe_choice', 'single', FALSE, array(
'options' => array(
'single' => \IPS\Member::loggedIn()->language()->addToStack( 'streamsubscription_guest_thing', FALSE, array('sprintf' => array( \IPS\core\Stream\Subscription::constructFromData($current)->stream->_title ) ) ),
'all' => \IPS\Member::loggedIn()->language()->addToStack( 'streamsubscription_guest_all', FALSE, array('pluralize' => array( $count )) ),
),
'descriptions' => array(
'single' => \IPS\Member::loggedIn()->language()->addToStack( 'follow_guest_unfollow_thing_desc' ),
'all' => \IPS\Member::loggedIn()->language()->addToStack( 'follow_guest_unfollow_all_desc', FALSE, array('sprintf' => array(base64_encode( \IPS\Http\Url::internal( 'app=core&module=system&controller=followed' ) ))) )
)
) ) );
}
if ( $values = $form->values() )
{
if( isset( $values['guest_unsubscribe_choice'] ) and $values['guest_unsubscribe_choice'] == 'all' )
{
foreach ( \IPS\core\Stream\Subscription::getSubscribedStreams( $member ) as $subscription )
{
$subscription->delete();
}
}
else
{
$subscription = \IPS\core\Stream\Subscription::loadByStreamAndMember( \IPS\core\Stream::load( $current['stream_id'] ), $member );
$subscription->delete();
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( '' ), 'unsubscribed' );
}
\IPS\Output::i()->sidebar['enabled'] = FALSE;
\IPS\Output::i()->bodyClasses[] = 'ipsLayout_minimal';
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'streams' )->unsubscribeStream( $title, $member, $form, !isset( \IPS\Request::i()->activity_stream_subscription ) ? FALSE : \IPS\Request::i()->activity_stream_subscription );
}
}
/**
* Rebuild logged in member's streams
@@ -814,4 +1004,4 @@ class _streams extends \IPS\Dispatcher\Controller
\IPS\Member::loggedIn()->member_streams = json_encode( array( 'default' => $default, 'streams' => iterator_to_array( \IPS\Db::i()->select( 'id, title', 'core_streams', array( '`member`=?', \IPS\Member::loggedIn()->member_id ), NULL, NULL, NULL, NULL, \IPS\Db::SELECT_FROM_WRITE_SERVER )->setKeyField('id')->setValueField('title') ) ) );
\IPS\Member::loggedIn()->save();
}
}
}
@@ -26,6 +26,14 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
* [Content\Controller] Class
*/
protected static $contentModel = 'IPS\core\Statuses\Status';
/**
* @brief These properties are used to specify datalayer context properties.
*
*/
public static $dataLayerContext = array(
'community_area' => [ 'value' => 'profile', 'odkUpdate' => 'true']
);
/**
* Main execute entry point - used to override breadcrumb
@@ -372,6 +380,27 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
NULL,
\IPS\Db::LOW_PRIORITY
);
/* Data Layer Stuff */
if ( \IPS\Settings::i()->core_datalayer_enabled )
{
try
{
$groupName = \IPS\Member\Group::load( $this->member->member_group_id )->formattedName;
}
catch ( \UnderflowException $e )
{
$groupName = null;
}
$properties = array(
'profile_group' => $groupName,
'profile_group_id' => $this->member->member_group_id ?: null,
'profile_id' => \intval( $this->member->member_id ) ?: null,
'profile_name' => $this->member->name ?: null,
'view_location' => 'page',
);
\IPS\core\DataLayer::i()->addEvent( 'social_view', $properties );
}
/* Get visitor data */
$visitors = $this->member->profileVisitors;
@@ -986,6 +1015,26 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
{
$this->_saveMember( $form, $values );
/* Data Layer Stuff */
if ( \IPS\Settings::i()->core_datalayer_enabled )
{
try
{
$groupName = \IPS\Member\Group::load( $this->member->member_group_id )->formattedName;
}
catch ( \UnderflowException $e )
{
$groupName = null;
}
$properties = array(
'profile_group' => $groupName,
'profile_group_id' => $this->member->member_group_id ?: null,
'profile_id' => \intval( $this->member->member_id ) ?: null,
'profile_name' => $this->member->name ?: null,
);
\IPS\core\DataLayer::i()->addEvent( 'social_update', $properties );
}
\IPS\Output::i()->redirect( $this->member->url() );
}
@@ -1968,7 +2017,7 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
$currentClass = $types[ $currentType ];
/* Build Output */
$url = \IPS\Http\Url::internal( "app=core&module=members&controller=profile&id={$this->member->member_id}&do=reputation&type={$currentType}", 'front', 'profile_reputation', array( $this->member->members_seo_name ) );
$url = \IPS\Http\Url::internal( "app=core&module=members&controller=profile&id={$this->member->member_id}&do=solutions&type={$currentType}", 'front', 'profile_solutions', array( $this->member->members_seo_name ) );
$table = new \IPS\Helpers\Table\Content( $currentClass, $url, NULL, NULL, \IPS\Content\Hideable::FILTER_AUTOMATIC, 'read', FALSE );
$table->joinContainer = TRUE;
@@ -2006,7 +2055,7 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
* @return void
*/
protected function recognize()
{
{
$class = \IPS\Request::i()->content_class;
$id = \IPS\Request::i()->content_id;
@@ -2041,27 +2090,27 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
$message = NULL;
if ( $this->member->todaysRecognizePoints() and $this->member->todaysRecognizeBadges() )
{
$message = \IPS\Member::loggedIn()->language()->addToStack('recognize_points_so_far_both', NULL, [
$message = \IPS\Member::loggedIn()->language()->addToStack( 'recognize_points_so_far_both', FALSE, [
'sprintf' => [
$this->member->name,
\IPS\Member::loggedIn()->language()->addToStack('recognize_points_pluralize', NULL, [ 'sprintf' => [ $this->member->todaysRecognizePoints() ], 'pluralize' => [ $this->member->todaysRecognizePoints() ] ]),
\IPS\Member::loggedIn()->language()->addToStack('recognize_badges_pluralize', NULL, [ 'sprintf' => [ $this->member->todaysRecognizeBadges() ], 'pluralize' => [ $this->member->todaysRecognizeBadges() ] ]),
\IPS\Member::loggedIn()->language()->addToStack( 'recognize_points_pluralize', FALSE, [ 'sprintf' => [ $this->member->todaysRecognizePoints() ], 'pluralize' => [ $this->member->todaysRecognizePoints() ] ]),
\IPS\Member::loggedIn()->language()->addToStack( 'recognize_badges_pluralize', FALSE, [ 'sprintf' => [ $this->member->todaysRecognizeBadges() ], 'pluralize' => [ $this->member->todaysRecognizeBadges() ] ]),
] ] );
}
else if ( $this->member->todaysRecognizePoints() )
{
$message = \IPS\Member::loggedIn()->language()->addToStack('recognize_points_so_far_single', NULL, [
$message = \IPS\Member::loggedIn()->language()->addToStack( 'recognize_points_so_far_single', FALSE, [
'sprintf' => [
$this->member->name,
\IPS\Member::loggedIn()->language()->addToStack('recognize_points_pluralize', NULL, [ 'sprintf' => [ $this->member->todaysRecognizePoints() ], 'pluralize' => [ $this->member->todaysRecognizePoints() ] ])
\IPS\Member::loggedIn()->language()->addToStack( 'recognize_points_pluralize', FALSE, [ 'sprintf' => [ $this->member->todaysRecognizePoints() ], 'pluralize' => [ $this->member->todaysRecognizePoints() ] ])
] ] );
}
else if ( $this->member->todaysRecognizeBadges() )
{
$message = \IPS\Member::loggedIn()->language()->addToStack('recognize_points_so_far_single', NULL, [
$message = \IPS\Member::loggedIn()->language()->addToStack( 'recognize_points_so_far_single', FALSE, [
'sprintf' => [
$this->member->name,
\IPS\Member::loggedIn()->language()->addToStack('recognize_points_pluralize', NULL, [ 'sprintf' => [ $this->member->todaysRecognizeBadges() ], 'pluralize' => [ $this->member->todaysRecognizeBadges() ] ])
\IPS\Member::loggedIn()->language()->addToStack( 'recognize_points_pluralize', FALSE, [ 'sprintf' => [ $this->member->todaysRecognizeBadges() ], 'pluralize' => [ $this->member->todaysRecognizeBadges() ] ])
] ] );
}
@@ -2072,11 +2121,11 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
$maxPoints = \IPS\Settings::i()->achievements_recognize_max_per_user_day - $this->member->todaysRecognizePoints();
if ( $maxPoints > 0 )
{
$message .= \IPS\Member::loggedIn()->language()->addToStack( 'recognize_points_so_far_limit', NULL, ['sprintf' => [$this->member->name, $maxPoints]] );
$message .= \IPS\Member::loggedIn()->language()->addToStack( 'recognize_points_so_far_limit', FALSE, ['sprintf' => [$this->member->name, $maxPoints]] );
}
else
{
$message .= \IPS\Member::loggedIn()->language()->addToStack( 'recognize_points_so_far_limit_none', NULL, ['sprintf' => [$this->member->name]] );
$message .= \IPS\Member::loggedIn()->language()->addToStack( 'recognize_points_so_far_limit_none', FALSE, ['sprintf' => [$this->member->name]] );
$disabled = TRUE;
}
@@ -2112,7 +2161,7 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
$form->add( new \IPS\Helpers\Form\Text( 'recognize_message', NULL, FALSE ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'recognize_public', TRUE, FALSE ) );
\IPS\Member::loggedIn()->language()->words['recognize_message_desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'recognize_message__desc', NULL, [ 'sprintf' => [ $content->author()->name ] ] );
\IPS\Member::loggedIn()->language()->words['recognize_message_desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'recognize_message__desc', FALSE, [ 'sprintf' => [ $content->author()->name ] ] );
if ( $values = $form->values() )
{
if ( ! $values['recognize_badge'] and ! $values['recognize_points'] )
@@ -2134,7 +2183,7 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
}
}
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('recognize_author', NULL, [ 'sprintf' => [ $content->author()->name ] ] );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'recognize_author', FALSE, [ 'sprintf' => [ $content->author()->name ] ] );
\IPS\Output::i()->output = $form->customTemplate( array( \IPS\Theme::i()->getTemplate( 'forms', 'core' ), 'popupTemplate' ) );
}
catch( \Exception $e )
@@ -44,11 +44,7 @@ class _messenger extends \IPS\Content\Controller
\IPS\Output::i()->error( 'no_module_permission_guest', '2C137/1', 403, '' );
}
if ( \IPS\Member::loggedIn()->members_disable_pm AND \IPS\Member::loggedIn()->members_disable_pm != 2 AND \IPS\Request::i()->do != 'enableMessenger' )
{
\IPS\Output::i()->error( \IPS\Member::loggedIn()->language()->addToStack('messenger_disabled_can_enable', FALSE, array( 'sprintf' => array( \IPS\Http\URL::internal( 'app=core&module=messaging&controller=messenger&do=enableMessenger', 'front', 'messaging' )->csrf() ) ) ), '2C137/A', 403, '' );
}
else if ( \IPS\Member::loggedIn()->members_disable_pm == 2 )
if ( \IPS\Member::loggedIn()->members_disable_pm == 2 )
{
\IPS\Output::i()->error( 'messenger_disabled', '2C137/9', 403, '' );
}
@@ -79,7 +75,8 @@ class _messenger extends \IPS\Content\Controller
protected function manage()
{
$baseUrl = \IPS\Http\Url::internal( "app=core&module=messaging&controller=messenger", 'front', 'messaging' );
$alert = NULL;
/* Get folders */
$folders = array( 'myconvo' => \IPS\Member::loggedIn()->language()->addToStack('messenger_folder_inbox') );
if ( \IPS\Member::loggedIn()->pconversation_filters )
@@ -201,6 +198,20 @@ class _messenger extends \IPS\Content\Controller
\IPS\Output::i()->redirect( $message->url() );
}
}
if( $conversation->alert )
{
try
{
$alert = \IPS\core\Alerts\Alert::load( $conversation->alert );
}
catch ( \OutOfRangeException $e ) {}
if( !$alert->forMember( \IPS\Member::loggedIn() ) )
{
$alert = NULL;
}
}
}
/* Default folder */
@@ -259,14 +270,21 @@ class _messenger extends \IPS\Content\Controller
if ( isset( \IPS\Request::i()->search ) and $searchValues = \IPS\Request::i()->valueFromArray('search') )
{
if ( isset( $searchValues['topic'] ) )
if ( isset( $searchValues['topic'] ) or isset( $searchValues['post'] ) )
{
$query[] = \IPS\Content\Search\Mysql\Query::matchClause( "core_message_topics.mt_title", \IPS\Request::i()->q, '+', FALSE );
}
if ( isset( $searchValues['post'] ) )
{
$query[] = \IPS\Content\Search\Mysql\Query::matchClause( "core_message_posts.msg_post", \IPS\Request::i()->q, '+', FALSE );
$fulltext = [];
if ( isset( $searchValues['topic'] ) )
{
$fulltext[] = \IPS\Content\Search\Mysql\Query::matchClause( "core_message_topics.mt_title", \IPS\Request::i()->q, '+', FALSE );
}
if ( isset( $searchValues['post'] ) )
{
$fulltext[] = \IPS\Content\Search\Mysql\Query::matchClause( "core_message_posts.msg_post", \IPS\Request::i()->q, '+', FALSE );
}
$query[] = "core_message_posts.msg_id IN( SELECT msg_id FROM {$prefix}core_message_posts as core_message_posts LEFT JOIN {$prefix}core_message_topics as core_message_topics ON core_message_posts.msg_topic_id=core_message_topics.mt_id WHERE (" . implode( " OR ", $fulltext ) . ") )";
}
if ( isset( $searchValues['recipient'] ) )
{
$query[] = "core_message_posts.msg_topic_id IN ( SELECT sender_map.map_topic_id FROM {$prefix}core_message_topic_user_map AS sender_map WHERE sender_map.map_is_starter=1 AND sender_map.map_user_id IN ( SELECT member_id FROM {$prefix}core_members AS sm WHERE name LIKE '" . \IPS\Db::i()->escape_string( \IPS\Request::i()->q ) . "%' ) )";
@@ -421,7 +439,7 @@ class _messenger extends \IPS\Content\Controller
{
if( \IPS\Request::i()->id )
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('messaging')->conversation( $conversation, $folders );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('messaging')->conversation( $conversation, $folders, $alert );
}
elseif( \IPS\Request::i()->q )
{
@@ -453,7 +471,7 @@ class _messenger extends \IPS\Content\Controller
}
else
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('messaging')->template( $folder, $folders, $folderCounts, $conversations, $pagination, $conversation, $baseUrl, ( $conversation ? 'messenger_convo' : 'messenger' ), ( \IPS\Request::i()->sortBy ? htmlspecialchars( \IPS\Request::i()->sortBy, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE ): 'mt_last_post_time' ), ( \IPS\Request::i()->filter ? htmlspecialchars( \IPS\Request::i()->filter, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE ) : '' ) );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('messaging')->template( $folder, $folders, $folderCounts, $conversations, $pagination, $conversation, $baseUrl, ( $conversation ? 'messenger_convo' : 'messenger' ), ( \IPS\Request::i()->sortBy ? htmlspecialchars( \IPS\Request::i()->sortBy, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE ): 'mt_last_post_time' ), ( \IPS\Request::i()->filter ? htmlspecialchars( \IPS\Request::i()->filter, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE ) : '' ), $alert );
}
}
@@ -469,6 +487,20 @@ class _messenger extends \IPS\Content\Controller
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('compose_new');
if( \IPS\Request::i()->alert )
{
try
{
$alert = \IPS\core\Alerts\Alert::load( \IPS\Request::i()->alert );
if( $alert->forMember( \IPS\Member::loggedIn() ) )
{
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('alert_modal_title', FALSE, array( 'sprintf' => array( $alert->title ) ) );
}
}
catch ( \OutOfRangeException $e ) {}
}
if( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->output = $form->customTemplate( array( \IPS\Theme::i()->getTemplate( 'forms', 'core' ), 'popupTemplate' ) );
@@ -779,7 +811,7 @@ class _messenger extends \IPS\Content\Controller
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('messenger_member_left', FALSE, array( 'sprintf' => array( $member->name ) ) ) );
}
if ( !$conversation::memberCanReceiveNewMessage( $member, \IPS\Member::loggedIn() ) )
if ( !$conversation::memberCanReceiveNewMessage( $member, \IPS\Member::loggedIn(), 'new' ) )
{
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('meesnger_err_bad_recipient', FALSE, array( 'sprintf' => array( $member->name ) ) ) );
}
@@ -945,7 +977,7 @@ class _messenger extends \IPS\Content\Controller
\IPS\Session::i()->csrfCheck();
\IPS\Member::loggedIn()->members_disable_pm = 1;
\IPS\Member::loggedIn()->save();
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( '' ), 'messenger_disabled' );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=messaging&controller=messenger', 'front', 'messaging' ), 'messenger_disabled' );
}
/**
@@ -37,6 +37,11 @@ class _modcp extends \IPS\Dispatcher\Controller
*/
public function __call( $method, $args )
{
if ( \IPS\Settings::i()->core_datalayer_enabled )
{
\IPS\core\DataLayer::i()->addContextProperty( 'community_area', \IPS\Lang::load( \IPS\Lang::defaultLanguage() )->addToStack('modcp'), true );
}
if ( isset( \IPS\Request::i()->do ) )
{
$activeTab = \IPS\Request::i()->tab ?: 'overview';
@@ -22,6 +22,14 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
*/
class _online extends \IPS\Dispatcher\Controller
{
/**
* @brief These properties are used to specify datalayer context properties.
*
*/
public static $dataLayerContext = array(
'community_area' => [ 'value' => 'online_user_list', 'odkUpdate' => 'true']
);
/**
* Show Online Users
*
@@ -22,6 +22,15 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
*/
class _ourpicks extends \IPS\Dispatcher\Controller
{
/**
* @brief These properties are used to specify datalayer context properties.
*
*/
public static $dataLayerContext = array(
'community_area' => [ 'value' => 'our_picks', 'odkUpdate' => 'true']
);
/**
* List promoted internally promoted items
*
@@ -589,7 +589,7 @@ class _promote extends \IPS\Dispatcher\Controller
if ( is_subclass_of( $class, 'IPS\Content' ) )
{
$promote->author_id = $object->author()->member_id;
$promote->author_id = $object->author()->member_id ?: 0;
}
$promote->save();
@@ -597,7 +597,7 @@ class _promote extends \IPS\Dispatcher\Controller
if ( $isNew )
{
/* Points */
$promote->author()->achievementAction( 'core', 'ContentPromotion', [
$promote->object()->author()->achievementAction( 'core', 'ContentPromotion', [
'content' => $promote->object(),
'promotype' => 'promote'
] );
@@ -27,6 +27,17 @@ class _search extends \IPS\Dispatcher\Controller
*/
protected $_cacheTimeout = 30 * 60;
/**
* @brief These properties are used to specify datalayer context properties.
*
* @example array(
'community_area' => array( 'value' => 'search', 'odkUpdate' => 'true' )
)
*/
public static $dataLayerContext = array(
'community_area' => array( 'value' => 'search', 'odkUpdate' => 'true' )
);
/**
* Check if this was a cached result we should just return a 304 against
*
@@ -80,7 +91,7 @@ class _search extends \IPS\Dispatcher\Controller
$form = $this->_form();
/* If we have the term, show the results */
if ( \IPS\Request::i()->isAjax() or isset( \IPS\Request::i()->q ) or isset( \IPS\Request::i()->tags ) or ( \IPS\Request::i()->type == 'core_members' and \IPS\Member::loggedIn()->canAccessModule( \IPS\Application\Module::get( 'core', 'members', 'front' ) ) ) )
if ( ( \IPS\Request::i()->isAjax() and ! isset( \IPS\Request::i()->_nodeSelectName ) ) or isset( \IPS\Request::i()->q ) or isset( \IPS\Request::i()->tags ) or ( \IPS\Request::i()->type == 'core_members' and \IPS\Member::loggedIn()->canAccessModule( \IPS\Application\Module::get( 'core', 'members', 'front' ) ) ) )
{
$showForm = ( \IPS\Request::i()->type == 'core_members' ) ? FALSE : ( !\IPS\Request::i()->isAjax() and !\IPS\Request::i()->q and !\IPS\Request::i()->tags );
@@ -105,6 +116,12 @@ class _search extends \IPS\Dispatcher\Controller
}
return;
}
/* Data Layer Event */
if ( \IPS\Settings::i()->core_datalayer_enabled AND !\IPS\Request::i()->isAjax() AND \IPS\Request::i()->q )
{
\IPS\core\DataLayer::i()->addEvent( 'search', array( 'query' => \IPS\Request::i()->q, ) );
}
return $this->_results();
}
@@ -153,7 +170,12 @@ class _search extends \IPS\Dispatcher\Controller
{
$baseUrl = $baseUrl->setQueryString( 'eitherTermsOrTags', \IPS\Request::i()->eitherTermsOrTags );
}
if( isset( \IPS\Request::i()->quick ) )
{
$baseUrl = $baseUrl->setQueryString( 'quick', \IPS\Request::i()->quick );
}
$types = static::contentTypes();
/* Flood control */
@@ -178,7 +200,8 @@ class _search extends \IPS\Dispatcher\Controller
\IPS\Request::i()->start_after = \IPS\Request::i()->joinedDate;
}
if ( isset( \IPS\Request::i()->start_before ) or isset( \IPS\Request::i()->start_after ) )
if ( ( isset( \IPS\Request::i()->start_before ) ) or ( isset( \IPS\Request::i()->start_after ) ) )
{
foreach ( array( 'before', 'after' ) as $l )
{
@@ -428,7 +451,7 @@ class _search extends \IPS\Dispatcher\Controller
$filter = \IPS\Content\Search\ContentFilter::init( $types[ \IPS\Request::i()->type ] );
$baseUrl = $baseUrl->setQueryString( 'type', \IPS\Request::i()->type );
if ( isset( \IPS\Request::i()->nodes ) )
if ( isset( \IPS\Request::i()->nodes ) and isset( $types[ \IPS\Request::i()->type ]::$containerNodeClass ) )
{
/* It's ok to just pass the (potential) parent IDs into the URL */
$baseUrl = $baseUrl->setQueryString( 'nodes', \IPS\Request::i()->nodes );
@@ -748,24 +771,27 @@ class _search extends \IPS\Dispatcher\Controller
/* Search tracking, only if there's a term for a non-member search and we're on the first page */
if( \IPS\Request::i()->q AND $page == 1 )
{
try
if( !\IPS\Member::loggedIn()->inGroup( json_decode( \IPS\Settings::i()->searchlog_exclude_groups ) ) )
{
\IPS\Db::i()->insert( 'core_statistics', array(
'type' => 'search',
'time' => time(),
'value_4' => mb_substr( \IPS\Request::i()->q, 0, 255 ), // Some search queries can be unnecessarily long
'value_2' => $count,
'extra_data' => md5( \IPS\Member::loggedIn()->email . \IPS\Member::loggedIn()->joined ) // Intentionally anonymized
) );
try
{
\IPS\Db::i()->insert( 'core_statistics', array(
'type' => 'search',
'time' => time(),
'value_4' => mb_substr( \IPS\Request::i()->q, 0, 255 ), // Some search queries can be unnecessarily long
'value_2' => $count,
'extra_data' => md5( \IPS\Member::loggedIn()->email . \IPS\Member::loggedIn()->joined ) // Intentionally anonymized
) );
}
catch( \IPS\Db\Exception $e ) {}
}
catch( \IPS\Db\Exception $e ) {}
}
/* Enable caching for the search results request */
if ( !\IPS\Member::loggedIn()->member_id )
{
$httpHeaders = array( 'Expires' => \IPS\DateTime::create()->add( new \DateInterval( 'PT3M' ) )->rfc1123(),
'Cache-Control' => "max-age=" . $this->_cacheTimeout . ", s-maxage=" . $this->_cacheTimeout . ", public, stale-if-error, stale-while-revalidate" );
'Cache-Control' => 'no-cache="Set-Cookie", max-age=' . $this->_cacheTimeout . ", s-maxage=" . $this->_cacheTimeout . ", public, stale-if-error, stale-while-revalidate" );
\IPS\Output::i()->httpHeaders += $httpHeaders;
}
@@ -813,6 +839,16 @@ class _search extends \IPS\Dispatcher\Controller
{
$form->hiddenValues['sortdirection'] = \IPS\Request::i()->sortdirection;
}
if ( isset( \IPS\Request::i()->nodes ) )
{
$form->hiddenValues['nodes'] = \IPS\Request::i()->nodes;
}
if ( isset( \IPS\Request::i()->quick ) )
{
$form->hiddenValues['quick'] = \IPS\Request::i()->quick;
}
/* If we do a quick search the type is set to 'all' but we want it as '' for the form to work properly */
if( isset( \IPS\Request::i()->type ) AND \IPS\Request::i()->type === 'all' )
@@ -22,6 +22,14 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
*/
class _directory extends \IPS\Dispatcher\Controller
{
/**
* @brief These properties are used to specify datalayer context properties.
*/
public static $dataLayerContext = array(
'community_area' => [ 'value' => 'staff', 'odkUpdate' => 'true']
);
/**
* Main execute entry point - used to override breadcrumb
*
@@ -656,15 +656,58 @@ class _ajax extends \IPS\Dispatcher\Controller
/**
* Return current CSRF token
*
* @return string|null
* @return void
*/
public function getCsrfKey(): ?string
public function getCsrfKey()
{
if ( ! \IPS\Member::loggedIn()->member_id )
if ( isset( \IPS\Request::i()->path ) )
{
\IPS\Output::i()->json( [ 'key' => \IPS\Session::i()->csrfKey ] );
$baseUrlData = parse_url( \IPS\Http\Url::baseUrl() );
/* If the baseURL was site.com/forums/, JS returns pathname which is /forums/foo
so we need to check for a path in the baseURL and make sure its removed from the
incoming path. We want to look for 'admin', so 'forums/admin' would confuse it */
$pathToUse = '';
if ( isset( $baseUrlData['path'] ) and $baseUrlData['path'] )
{
$pathToUse .= trim( $baseUrlData['path'], '/' );
}
$path = trim( \IPS\Request::i()->path, '/' );
if ( $pathToUse )
{
$path = trim( preg_replace( '#^' . $pathToUse . '#', '', $path ), '/' );
}
$bits = explode( '/', $path );
/* This is an ACP URL, so we need the admin session */
if ( $bits[0] == \IPS\CP_DIRECTORY )
{
/* Ask ajax to follow the redirect for the Admin session CSRF */
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=system&controller=login&do=getCsrfKey', 'admin' ) );
}
}
return NULL;
\IPS\Output::i()->json( [ 'key' => \IPS\Session::i()->csrfKey ] );
}
/**
* Get any events in the session cache and clear the cache
*
* @return void
*/
public function getDataLayerEvents()
{
$payload = '{}';
if ( \IPS\Settings::i()->core_datalayer_enabled AND \IPS\Member::loggedIn()->member_id )
{
$payload = \IPS\core\DataLayer::i()->jsonEvents;
\IPS\core\DataLayer::i()->clearCache();
}
/* Using this instead of \IPS\Output::i()->json because it's already JSON encoded */
\IPS\Output::i()->sendOutput( \IPS\Member::loggedIn()->language()->stripVLETags( $payload ), 200, 'application/json', \IPS\Output::i()->httpHeaders );
}
}
@@ -0,0 +1,62 @@
<?php
/**
* @brief alerts
* @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 May 2022
*/
namespace IPS\core\modules\front\system;
/* 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;
}
/**
* alerts
*/
class _alerts extends \IPS\Dispatcher\Controller
{
/**
* Execute
*
* @return void
*/
public function execute()
{
parent::execute();
}
/**
* Dismiss alert
*
* @return void
*/
protected function dismiss()
{
\IPS\Session::i()->csrfCheck();
/* Update user last seen */
try
{
$alert = \IPS\core\Alerts\Alert::load( \IPS\Request::i()->id );
if( $alert->reply == 2 and $alert->author()->member_id )
{
\IPS\Output::i()->error( 'alert_cant_dismiss', '3C428/1', 403, '' );
}
$alert->dismiss();
}
catch( \OutOfRangeException $e ) {}
/* Redirect */
\IPS\Output::i()->redirect( base64_decode( \IPS\Request::i()->ref ) );
}
}
@@ -123,7 +123,7 @@ class _announcement extends \IPS\Content\Controller
/* Get groups that cannot view the item */
$groups = $item->cannotViewGroups();
if( !\count( $groups ) )
if( !$groups )
{
\IPS\Output::i()->json( array( 'status' => 'all_permissions' ) );
}
@@ -0,0 +1,65 @@
<?php
/**
* @brief Easy mode controller
* @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 15 Oct 2021
*/
namespace IPS\core\modules\front\system;
/* 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;
}
/**
* Redirect
*/
class _easymode extends \IPS\Dispatcher\Controller
{
/**
* Proxy remote CSS URLs to allow for JS to select classes
*
* @return void
*/
protected function proxy()
{
if ( \IPS\Theme::isUsingEasyModeEditor() )
{
if ( isset( \IPS\Request::i()->f ) )
{
$url = base64_decode( \IPS\Request::i()->f );
$storage = \IPS\File::getClass( 'core_Theme' );
$storageUrl = parse_url( $storage->baseUrl() );
$test = parse_url( $url );
/* Does this URL match the host of the storage URL? */
if ( preg_match( "/\.css(\.gz)?(\?|$)/", $url ) and isset( $test['host'] ) and $test['host'] == $storageUrl['host'] )
{
$encoded = ( preg_match( "/\.css\.gz?(\?|$)/", $url ) );
try
{
$css = \IPS\Http\Url::external( $url )->setScheme('https')->request()->get();
if ( $encoded )
{
$css = gzdecode( $css );
}
\IPS\Output::i()->sendOutput( $css, 200, 'text/css' );
}
catch( \Exception $e ) { }
}
}
}
/* still here? */
\IPS\Output::i()->sendOutput( '/* Could not read ' . $url . '*/', 200, 'text/css' );
}
}
@@ -344,7 +344,7 @@ class _editor extends \IPS\Dispatcher\Controller
$results = '';
if ( mb_strlen( \IPS\Request::i()->input ) > 0 )
{
foreach ( \IPS\Db::i()->select( '*', 'core_members', array( \IPS\Db::i()->like( 'name', \IPS\Request::i()->input ) ), 'name', 10 ) as $row )
foreach ( \IPS\Db::i()->select( '*', 'core_members', array( \IPS\Db::i()->like( 'name', \IPS\Request::i()->input ), array( 'temp_ban !=?', '-1' ) ), 'name', 10 ) as $row )
{
$results .= \IPS\Theme::i()->getTemplate( 'editor', 'core', 'global' )->mentionRow( \IPS\Member::constructFromData( $row ) );
}
@@ -440,4 +440,48 @@ class _editor extends \IPS\Dispatcher\Controller
\IPS\Output::i()->json( $results );
}
}
/**
* Get stored replies
*
* @return void
*/
protected function storedReplies()
{
if ( isset( \IPS\Request::i()->id ) )
{
try
{
$reply = \IPS\core\StoredReplies::loadAndCheckPerms( \IPS\Request::i()->id );
\IPS\Output::i()->json( [
'id' => $reply->id,
'title' => $reply->title,
'reply' => $reply->text
] );
}
catch( \Exception $e )
{
\IPS\Output::i()->json( [ 'error' => \IPS\Theme::i()->getTemplate( 'system', 'core' )->noResults() ] );
}
}
else
{
$results = [];
foreach ( \IPS\core\StoredReplies::roots() as $reply )
{
if ( $reply->enabled )
{
$results[] = ['id' => $reply->id, 'title' => $reply->title];
}
}
if ( empty( $results ) )
{
\IPS\Output::i()->json( [ 'error' => \IPS\Theme::i()->getTemplate( 'system', 'core' )->noResults() ] );
}
\IPS\Output::i()->json( $results );
}
}
}
@@ -136,6 +136,10 @@ class _lostpass extends \IPS\Dispatcher\Controller
{
/* If we have an existing validation record, we can just reuse it */
$sendEmail = TRUE;
/* Delete any lost pass validating records that are older than 45 minutes - These records are only valid for one hour. */
\IPS\Db::i()->delete( 'core_validating', [ 'member_id=? AND lost_pass=1 AND entry_date<?', $member->member_id, time() - 2700 ] );
try
{
$existing = \IPS\Db::i()->select( array( 'vid', 'email_sent' ), 'core_validating', array( 'member_id=? AND lost_pass=1', $member->member_id ) )->first();
@@ -200,6 +204,12 @@ class _lostpass extends \IPS\Dispatcher\Controller
{
\IPS\Output::i()->error( 'no_validation_key', '2S151/1', 410, '' );
}
/* Show a nicer error message if their link has expired */
if( $record['entry_date'] < \IPS\DateTime::create()->sub( new \DateInterval( 'PT1H' ) )->getTimestamp() )
{
\IPS\Output::i()->error( 'lost_pass_expired', '2S151/4', 410, '' );
}
/* Show form for new password */
$form = new \IPS\Helpers\Form( "resetpass", 'save' );
@@ -177,7 +177,7 @@ class _metatags extends \IPS\Dispatcher\Controller
$manifest = json_decode( \IPS\Settings::i()->manifest_details, TRUE );
$output = array(
'scope' => '/',
'scope' => rtrim( \IPS\Settings::i()->base_url, '/' ) . '/',
'name' => \IPS\Settings::i()->board_name,
'theme_color' => \IPS\Theme::i()->settings['header']
);
@@ -191,6 +191,7 @@ class _metatags extends \IPS\Dispatcher\Controller
}
$homeScreen = json_decode( \IPS\Settings::i()->icons_homescreen, TRUE ) ?? array();
$homeScreenMaskable = json_decode( \IPS\Settings::i()->icons_homescreen_maskable, TRUE ) ?? array();
foreach( $homeScreen as $k => $v )
{
@@ -206,35 +207,30 @@ class _metatags extends \IPS\Dispatcher\Controller
$output['icons'][] = array(
'src' => (string) $file->url,
'type' => \IPS\File::getMimeType( $file->originalFilename ),
'sizes' => $v['width'] . 'x' . $v['height']
);
'sizes' => $v['width'] . 'x' . $v['height'],
'purpose' => 'any'
);;
}
}
if ( \IPS\Settings::i()->mobile_app_prompt )
foreach( $homeScreenMaskable as $k => $v )
{
if ( !isset( $output['short_name'] ) )
if( mb_strpos( $k, 'android' ) !== FALSE )
{
$output['short_name'] = \IPS\Settings::i()->board_name;
if( !isset( $output['icons'] ) )
{
$output['icons'] = array();
}
$file = \IPS\File::get( 'core_Icons', $v['url'] );
$output['icons'][] = array(
'src' => (string) $file->url,
'type' => \IPS\File::getMimeType( $file->originalFilename ),
'sizes' => $v['width'] . 'x' . $v['height'],
'purpose' => 'maskable'
);;
}
if ( !isset( $output['display'] ) )
{
$output['display'] = 'standalone';
}
$output['prefer_related_applications'] = TRUE;
$output['related_applications'] = array(
array(
'platform' => 'play',
'id' => \IPS\Settings::i()->mobile_app_play_id ?: \IPS\APP_MULTICOMMUNITY_PLAY_ID,
'url' => \IPS\Settings::i()->mobile_app_play_url ?: \IPS\APP_MULTICOMMUNITY_PLAY_URL,
),
array(
'platform' => 'itunes',
'id' => \IPS\Settings::i()->mobile_app_itunes_id ?: \IPS\APP_MULTICOMMUNITY_ITUNES_ID, // Note: this is also defined in includeMeta because iOS handles this different.
'url' => \IPS\Settings::i()->mobile_app_itunes_url ?: \IPS\APP_MULTICOMMUNITY_ITUNES_URL,
),
);
}
\IPS\Data\Store::i()->manifest = $output;
@@ -519,8 +519,8 @@ class _notifications extends \IPS\Dispatcher\Controller
$form->hiddenValues[ $k ] = $v;
if ( empty( $notificationConfiguration ) )
{
$type = $type == 'follower_content' ? 'core_Content' : $type;
$form->addMessage( \IPS\Member::loggedIn()->language()->addToStack( 'follow_type_no_config' ) . ' <a href="' . \IPS\Http\Url::internal( 'app=core&module=system&controller=notifications&do=options&type=' . $type, 'front', 'notifications_options' ) . '">' . \IPS\Member::loggedIn()->language()->addToStack( 'notification_options', FALSE ) . '</a>', 'ipsPadding:none', FALSE );
}
else
{
@@ -601,15 +601,18 @@ class _notifications extends \IPS\Dispatcher\Controller
/* Send notification if following member */
if( $class == "IPS\Member" )
{
/* Give points */
$receiver = \IPS\Member::load( \IPS\Request::i()->follow_rel_id );
$receiver->achievementAction( 'core', 'FollowMember', [
'giver' => \IPS\Member::loggedIn()
] );
if( $values['follow_public'] )
{
/* Give points */
$receiver = \IPS\Member::load( \IPS\Request::i()->follow_id );
$receiver->achievementAction( 'core', 'FollowMember', [
'giver' => \IPS\Member::loggedIn()
] );
$notification = new \IPS\Notification( \IPS\Application::load( 'core' ), 'member_follow', \IPS\Member::loggedIn(), array( \IPS\Member::loggedIn() ) );
$notification->recipients->attach( $thing );
$notification->send();
$notification = new \IPS\Notification( \IPS\Application::load( 'core' ), 'member_follow', \IPS\Member::loggedIn(), array( \IPS\Member::loggedIn() ) );
$notification->recipients->attach( $thing );
$notification->send();
}
}
else if ( \in_array( 'IPS\Node\Model', class_parents( $class ) ) )
{
@@ -1061,8 +1064,9 @@ class _notifications extends \IPS\Dispatcher\Controller
}
\IPS\Db::i()->delete( 'core_follow', array( 'follow_app=? AND follow_area=? AND follow_rel_id=?', \IPS\Request::i()->follow_app, \IPS\Request::i()->follow_area, \IPS\Request::i()->follow_id ) );
if( \IPS\Request::i()->follow_area == 'club' )
\IPS\Db::i()->delete( 'core_follow_count_cache', array( 'id=? AND class=?', \IPS\Request::i()->follow_id, 'IPS\\' . \IPS\Request::i()->follow_app . '\\' . mb_ucfirst( \IPS\Request::i()->follow_area ) ) );
if( \IPS\Request::i()->follow_area == 'club' )
{
try
{
@@ -34,11 +34,12 @@ class _redirect extends \IPS\Dispatcher\Controller
*/
protected function manage()
{
/* First check the key to make sure this actually came from HTMLPurifier */
if ( \IPS\Login::compareHashes( hash_hmac( "sha256", \IPS\Request::i()->url, \IPS\Settings::i()->site_secret_key ), (string) \IPS\Request::i()->key ) OR \IPS\Login::compareHashes( hash_hmac( "sha256", \IPS\Request::i()->url, \IPS\Settings::i()->site_secret_key . 'r' ), (string) \IPS\Request::i()->key ) )
/* The URL may have had a line-break added in the outbound email if it's a long URL, we need to fix that. Then check the key matches. */
$url = str_replace( [ '%20','%0D' ], '', (string) \IPS\Http\Url::createFromString( \IPS\Request::i()->url ) );
if ( \IPS\Login::compareHashes( hash_hmac( "sha256", $url, \IPS\Settings::i()->site_secret_key ), (string) \IPS\Request::i()->key ) OR \IPS\Login::compareHashes( hash_hmac( "sha256", $url, \IPS\Settings::i()->site_secret_key . 'r' ), (string) \IPS\Request::i()->key ) )
{
/* Construct the URL */
$url = \IPS\Http\Url::external( \IPS\Request::i()->url );
$url = \IPS\Http\Url::external( $url );
/* If this is coming from email tracking, log the click */
if( isset( \IPS\Request::i()->email ) AND \IPS\Settings::i()->prune_log_emailstats != 0 )
@@ -42,7 +42,8 @@ class _register extends \IPS\Dispatcher\Controller
if( \IPS\Request::i()->do !== 'complete' and \IPS\Request::i()->do !== 'setPassword'
and \IPS\Request::i()->do !== 'changeEmail' and \IPS\Request::i()->do !== 'validate'
and \IPS\Request::i()->do !== 'validating' and \IPS\Request::i()->do !== 'reconfirm'
and \IPS\Request::i()->do !== 'finish' and \IPS\Request::i()->do !== 'cancel' )
and \IPS\Request::i()->do !== 'finish' and \IPS\Request::i()->do !== 'cancel'
and \IPS\Request::i()->do !== 'resend' )
{
if ( \IPS\Login::registrationType() == 'redirect' )
{
@@ -60,6 +61,7 @@ class _register extends \IPS\Dispatcher\Controller
\IPS\Output::i()->bodyClasses[] = 'ipsLayout_minimalNoHome';
}
\IPS\Output::i()->sidebar['enabled'] = FALSE;
\IPS\Output::i()->pageCaching = FALSE;
\IPS\Output::i()->linkTags['canonical'] = (string) \IPS\Http\Url::internal( 'app=core&module=system&controller=register', 'front', 'register' );
}
@@ -1057,9 +1059,11 @@ class _register extends \IPS\Dispatcher\Controller
*/
protected function cancel()
{
/* This bit is kind of important */
/* This bit is kind of important - don't allow externally created accounts to be deleted, they could already have commerce data */
\IPS\Session::i()->csrfCheck();
if ( \IPS\Member::loggedIn()->name and \IPS\Member::loggedIn()->email and !\IPS\Db::i()->select( 'COUNT(*)', 'core_validating', array( 'member_id=? AND new_reg=1', \IPS\Member::loggedIn()->member_id ) )->first() )
if ( (\IPS\Member::loggedIn()->name and \IPS\Member::loggedIn()->email
and !\IPS\Db::i()->select( 'COUNT(*)', 'core_validating', array( 'member_id=? AND new_reg=1', \IPS\Member::loggedIn()->member_id ) )->first() )
OR \IPS\Member::loggedIn()->members_bitoptions['created_externally'] )
{
\IPS\Output::i()->error( 'no_module_permission', '2C223/1', 403, '' );
}
@@ -1279,7 +1283,7 @@ class _register extends \IPS\Dispatcher\Controller
\IPS\Output::i()->error( 'node_error', '2C223/G', 403, '' );
}
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'set_password_title', NULL, array( 'sprintf' => array( $member->name ) ) );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'set_password_title', FALSE, array( 'sprintf' => array( $member->name ) ) );
if ( $mfa = \IPS\MFA\MFAHandler::accessToArea( 'core', 'AuthenticateFront', \IPS\Request::i()->url(), $member ) )
{
\IPS\Output::i()->output = $mfa;
@@ -51,8 +51,6 @@ class _serviceworker extends \IPS\Dispatcher\Controller
/* VARIABLES TO PASS THROUGH TO JS */
$DEBUG = ( ( \IPS\IN_DEV and \IPS\DEV_DEBUG_JS ) or \IPS\DEBUG_JS ) ? 'true' : 'false'; // Weird casting is intentional.
$SW_CACHE_BUST = \IPS\SW_CACHE_BUST ? 'true' : 'false'; // Weird casting is intentional.
$HTTP_CACHE_DURATION = \IPS\CACHE_PAGE_TIMEOUT;
$BASE_URL = \IPS\Settings::i()->base_url;
$CACHED_ASSETS = json_encode( $cachedUrls, JSON_UNESCAPED_SLASHES );
$OFFLINE_URL = (string) \IPS\Http\Url::internal("app=core&module=system&controller=offline", "front", "user_offline");
@@ -64,12 +62,9 @@ class _serviceworker extends \IPS\Dispatcher\Controller
$output = <<<JAVASCRIPT
"use strict";
const DEBUG = {$DEBUG};
const SW_CACHE_BUST = {$SW_CACHE_BUST};
const BASE_URL = "{$BASE_URL}";
const CACHED_ASSETS = {$CACHED_ASSETS};
const CACHE_NAME = 'invision-community-{$CACHE_VERSION}';
const HTTP_CACHE_DURATION = {$HTTP_CACHE_DURATION};
const HTTP_CACHE_BUFFER = 60;
const OFFLINE_URL = "{$OFFLINE_URL}";
const NOTIFICATION_ICON = {$NOTIFICATION_ICON};
const DEFAULT_NOTIFICATION_TITLE = "{$DEFAULT_NOTIFICATION_TITLE}";
@@ -22,6 +22,13 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
*/
class _settings extends \IPS\Dispatcher\Controller
{
/**
* @brief These properties are used to specify datalayer context properties.
*
*/
public static $dataLayerContext = array(
'community_area' => [ 'value' => 'settings', 'odkUpdate' => true]
);
/**
* Execute
@@ -384,7 +391,10 @@ class _settings extends \IPS\Dispatcher\Controller
$form = new \IPS\Helpers\Form;
$form->class = 'ipsForm_collapseTablet';
$form->add( new \IPS\Helpers\Form\Password( 'current_password', '', TRUE, array( 'protect' => TRUE, 'validateFor' => \IPS\Member::loggedIn(), 'bypassProfanity' => \IPS\Helpers\Form\Text::BYPASS_PROFANITY_ALL, 'htmlAutocomplete' => "current-password" ) ) );
if ( !\IPS\Member::loggedIn()->members_bitoptions['password_reset_forced'] )
{
$form->add( new \IPS\Helpers\Form\Password( 'current_password', '', TRUE, array( 'protect' => TRUE, 'validateFor' => \IPS\Member::loggedIn(), 'bypassProfanity' => \IPS\Helpers\Form\Text::BYPASS_PROFANITY_ALL, 'htmlAutocomplete' => "current-password" ) ) );
}
$form->add( new \IPS\Helpers\Form\Password( 'new_password', '', TRUE, array( 'protect' => TRUE, 'showMeter' => \IPS\Settings::i()->password_strength_meter, 'checkStrength' => TRUE, 'strengthMember' => \IPS\Member::loggedIn(), 'bypassProfanity' => \IPS\Helpers\Form\Text::BYPASS_PROFANITY_ALL, 'htmlAutocomplete' => "new-password" ) ) );
$form->add( new \IPS\Helpers\Form\Password( 'confirm_new_password', '', TRUE, array( 'protect' => TRUE, 'confirm' => 'new_password', 'bypassProfanity' => \IPS\Helpers\Form\Text::BYPASS_PROFANITY_ALL, 'htmlAutocomplete' => "new-password" ) ) );
@@ -597,6 +607,8 @@ class _settings extends \IPS\Dispatcher\Controller
*/
protected function _mfa()
{
\IPS\Output::i()->bypassCsrfKeyCheck = true;
/* Validate password */
if ( !isset( $_SESSION['passwordValidatedForMfa'] ) )
{
@@ -646,11 +658,11 @@ class _settings extends \IPS\Dispatcher\Controller
return $output . $mfaOutput;
}
/* Do any enabling/diabling */
/* Do any enabling/disabling */
if ( isset( \IPS\Request::i()->act ) )
{
\IPS\Session::i()->csrfCheck();
/* Get the handler */
$key = \IPS\Request::i()->type;
if ( !isset( $handlers[ $key ] ) or \IPS\MFA\MFAHandler::accessToArea( 'core', 'SecurityQuestions', \IPS\Http\Url::internal( 'app=core&module=system&controller=settings&area=mfa', 'front', 'settings_mfa' ) ) )
@@ -671,11 +683,11 @@ class _settings extends \IPS\Dispatcher\Controller
\IPS\Member::loggedIn()->members_bitoptions['security_questions_opt_out'] = FALSE;
\IPS\Member::loggedIn()->save();
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=system&controller=settings&area=mfa', 'front', 'settings_mfa' ) );
}
/* Show the configruation modal */
/* Show the configuration modal */
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('settings');
return $output . \IPS\Theme::i()->getTemplate( 'system' )->settingsMfaSetup( $handlers[ $key ]->configurationScreen( \IPS\Member::loggedIn(), FALSE, \IPS\Http\Url::internal( 'app=core&module=system&controller=settings&area=mfa&act=enable&type=' . $key, 'front', 'settings_mfa' ) ), \IPS\Http\Url::internal( 'app=core&module=system&controller=settings&area=mfa&act=enable&type=' . $key, 'front', 'settings_mfa' ) );
}
@@ -1708,7 +1720,7 @@ class _settings extends \IPS\Dispatcher\Controller
$this->_performRedirect( \IPS\Http\Url::internal( "app=core&module=system&controller=settings", 'front', 'settings' ), 'block_newsletter_subscribed' );
}
/**
* Toggle Anonymously Online
*
@@ -1717,26 +1729,57 @@ class _settings extends \IPS\Dispatcher\Controller
protected function updateAnon()
{
\IPS\Session::i()->csrfCheck();
/* Check this value can be toggled */
if( \IPS\Member::loggedIn()->group['g_hide_online_list'] >= 1 )
{
\IPS\Output::i()->error( 'online_status_cannot_change', '2C122/X', 403, '' );
}
/* Update the bitwise flag */
\IPS\Member::loggedIn()->members_bitoptions['is_anon'] = (bool) \IPS\Request::i()->value;
\IPS\Member::loggedIn()->save();
/* Update users devices */
foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'core_members_known_devices', array( "member_id=?", \IPS\Member::loggedIn()->member_id ) ), 'IPS\Member\Device' ) AS $device )
{
$device->anonymous = ( (bool) \IPS\Request::i()->value ) ? 1 : 0;
$device->save();
}
/* Update the session */
\IPS\Session::i()->setType( ( (bool) \IPS\Request::i()->value ) ? \IPS\Session\Front::LOGIN_TYPE_ANONYMOUS : \IPS\Session\Front::LOGIN_TYPE_MEMBER );
if ( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->json( 'OK' );
}
$this->_performRedirect( \IPS\Http\Url::internal( "app=core&module=system&controller=settings&area=mfa", 'front', 'settings_mfa' ), 'saved' );
}
/**
* Toggle Whether PII is included in the data layer
*
* @return void
*/
protected function togglePii()
{
if ( ! \IPS\Settings::i()->core_datalayer_member_pii_choice )
{
\IPS\Output::i()->error( 'page_not_found', '3T251/7', 404 );
}
\IPS\Session::i()->csrfCheck();
/* Update the bitwise flag */
\IPS\Member::loggedIn()->datalayer_collect_pii = (bool) ! \IPS\Member::loggedIn()->datalayer_collect_pii;
\IPS\Member::loggedIn()->save();
if ( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->json( 'OK' );
}
$this->_performRedirect( \IPS\Http\Url::internal( "app=core&module=system&controller=settings&area=mfa", 'front', 'settings_mfa' ), 'saved' );
}
@@ -114,7 +114,7 @@ class _vse extends \IPS\Dispatcher\Controller
$rgb[] = hexdec( \substr( $colorValue, 4, 2 ) );
}
$colorValues[ $key ] = implode( $rgb, ', ');
$colorValues[ $key ] = implode( ', ', $rgb );
}
}
@@ -246,7 +246,7 @@ class _widgets extends \IPS\Dispatcher\Controller
\IPS\Output::i()->json( 'OK' );
}
\IPS\Member::loggedIn()->language()->words['widget_adv__custom_desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'widget_adv__custom__desc', NULL, array( 'sprintf' => array( \IPS\Request::i()->block ) ) );
\IPS\Member::loggedIn()->language()->words['widget_adv__custom_desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'widget_adv__custom__desc', FALSE, array( 'sprintf' => array( \IPS\Request::i()->block ) ) );
\IPS\Output::i()->output = $configurationForm->customTemplate( array( \IPS\Theme::i()->getTemplate( 'widgets', 'core' ), 'formTemplate' ), $widget );
}
}
@@ -84,7 +84,7 @@ class _applications extends \IPS\Dispatcher\Controller
$apps[ (string) $app ] = array(
'name' => (string) $name,
'disabled' => ( !empty( $errors ) or (string) $app === 'core' ),
'disabled' => ( !empty( $errors ) or ( (string) $app === 'core' or (string) $app === 'cloud' ) ),
'errors' => $errors,
);
}
@@ -93,6 +93,11 @@ class _applications extends \IPS\Dispatcher\Controller
/* Bring core app to top */
$system['core'] = $apps['core'];
unset( $apps['core'] );
if ( isset( $apps['cloud'] ) )
{
$system['cloud'] = $apps['cloud'];
unset( $apps['cloud'] );
}
$apps = array_merge( $system, $apps );
$form->add( new \IPS\Helpers\Form\Custom( 'apps', $defaultTicks, TRUE, array(
@@ -120,6 +125,7 @@ class _applications extends \IPS\Dispatcher\Controller
$options = array_combine( array_keys( $apps ), array_map( function( $value ){ return $value['name']; }, $apps ) );
/* But put system app at end of list to ensure it's not selected by default over something else */
unset( $options['core'] );
unset( $options['cloud'] );
$options['core'] = $apps['core']['name'];
return \IPS\Theme::i()->getTemplate( 'forms' )->select( 'default_app', $element->value, TRUE, $options );
},
@@ -30,7 +30,7 @@ class _license extends \IPS\Dispatcher\Controller
public function manage()
{
$form = new \IPS\Helpers\Form( 'license', 'continue', \IPS\Http\Url::external( ( \IPS\Request::i()->isSecure() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME'] . '?controller=license' ) );
$form->add( new \IPS\Helpers\Form\Text( 'lkey', 'Nulled by IPBMafia.ru | 09.06.2021', TRUE, array( 'size' => 50 ), function( $val )
$form->add( new \IPS\Helpers\Form\Text( 'lkey', 'Nulled by IPBMafia.ru | 06.07.2022', TRUE, array( 'size' => 50 ), function( $val )
{
\IPS\IPS::checkLicenseKey( $val, ( \IPS\Request::i()->isSecure() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . mb_substr( $_SERVER['SCRIPT_NAME'], 0, -mb_strlen( \IPS\CP_DIRECTORY . '/install/index.php' ) ) );
}, NULL, '<a href="' . \IPS\Http\Url::ips( 'docs/find_lkey' ) . '" target="_blank" rel="noopener">' . \IPS\Member::loggedIn()->language()->addToStack('lkey_help') . '</a>' ) );
@@ -58,7 +58,7 @@ class _applications extends \IPS\Dispatcher\Controller
foreach( \IPS\Application::applications() as $app => $data )
{
$path = \IPS\ROOT_PATH . '/applications/' . $app;
$path = \IPS\Application::getRootPath( $app ) . '/applications/' . $app;
if ( $app == 'chat' )
{
@@ -118,15 +118,19 @@ class _applications extends \IPS\Dispatcher\Controller
'errors' => $errors,
'current' => \IPS\Application::load( $app )->version,
'available' => \IPS\Application::getAvailableVersion( $app, TRUE ),
'force' => ( $app === 'core' and $availableVersion > $currentVersion )
'force' => ( ( $app === 'core' or $app === 'cloud' ) and $availableVersion > $currentVersion )
);
}
/* Bring core app to top */
$system['core'] = $apps['core'];
unset( $apps['core'] );
if ( isset( $apps['cloud'] ) )
{
$system['cloud'] = $apps['cloud'];
unset( $apps['cloud'] );
}
$apps = array_merge( $system, $apps );
if( \count( $defaultTicks ) )
{
$form = new \IPS\Helpers\Form( 'applications', 'continue' );
@@ -154,6 +158,11 @@ class _applications extends \IPS\Dispatcher\Controller
{
$_SESSION['apps']['core'] = 'core';
}
if ( isset( $apps['cloud'] ) and $apps['cloud']['force'] )
{
$_SESSION['apps']['cloud'] = 'cloud';
}
if( \count( $values['apps'] ) )
{
@@ -80,7 +80,7 @@ class _login extends \IPS\Dispatcher\Controller
$canUpgrade = FALSE;
foreach( \IPS\Application::applications() as $app => $data )
{
$path = \IPS\ROOT_PATH . '/applications/' . $app;
$path = \IPS\Application::getRootPath( $app ) . '/applications/' . $app;
if ( $app != 'chat' and is_dir( $path . '/data' ) )
{