Version 4.6.0

This commit is contained in:
Neo committed 2025-12-19 05:55:31 -08:00
1 parent f79dcf067a
commit 517a5e1f70
2036 files changed
+110041 -26162

No files matched your search

@@ -0,0 +1,281 @@
<?php
/**
* @brief Achievement badges
* @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 Feb 2021
*/
namespace IPS\core\modules\admin\achievements;
/* 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;
}
/**
* Achievement badges
*/
class _badges extends \IPS\Node\Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* Node Class
*/
protected $nodeClass = 'IPS\core\Achievements\Badge';
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'badges_manage' );
parent::execute();
}
/**
* Manage
*
* @return void
*/
protected function manage()
{
if( $data = \IPS\core\Achievements\Rule::getRebuildProgress() )
{
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'achievements' )->rebuildProgress( $data, TRUE );
}
\IPS\Output::i()->sidebar['actions']['export'] = array(
'primary' => false,
'icon' => 'cloud-download',
'link' => \IPS\Http\Url::internal('app=core&module=achievements&controller=badges&do=exportForm'),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('acp_achievements_export') ),
'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',
);
return parent::manage();
}
/**
* Fetch any additional HTML for this row
*
* @param object $node Node returned from $nodeClass::load()
* @return NULL|string
*/
public function _getRowHtml( $node )
{
$count = \IPS\Db::i()->select( 'COUNT(*)', 'core_member_badges', [ 'badge=?', $node->id ] )->first();
$url = \IPS\Http\Url::internal("app=core&module=achievements&controller=badges&do=view&id={$node->id}");
return \IPS\Theme::i()->getTemplate('achievements')->memberCount( $url, $count );
}
/**
* View who has earned a badge
*
* @return void
*/
public function view()
{
$badge = \IPS\core\Achievements\Badge::load( \IPS\Request::i()->id );
$table = new \IPS\Helpers\Table\Db( 'core_member_badges', \IPS\Http\Url::internal("app=core&module=achievements&controller=badges&do=view&id={$badge->id}"), [ 'badge=?', $badge->id ] );
$table->joins[] = [
'select' => 'action,identifier',
'from' => 'core_achievements_log',
'where' => 'core_achievements_log.id=core_member_badges.action_log',
'type' => 'LEFT'
];
$table->langPrefix = 'badge_log_';
$table->include = [ 'member', 'action_log', 'rule', 'datetime' ];
$table->sortBy = $table->sortBy ?: 'datetime';
$table->advancedSearch = array(
'datetime' => \IPS\Helpers\Table\SEARCH_DATE_RANGE,
);
$table->parsers = array(
'member' => function ( $val, $row )
{
return \IPS\Theme::i()->getTemplate( 'global', 'core' )->userLink( \IPS\Member::load( $val ) );
},
'action_log' => function( $val, $row ) {
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
{
return \IPS\Member::loggedIn()->language()->addToStack('unknown');
}
},
'rule' => function( $val, $row ) {
$rule = NULL;
if ( $val )
{
try
{
$rule = \IPS\core\Achievements\Rule::load( $val );
}
catch ( \OutOfRangeException $e ) { }
}
if ( $rule )
{
return \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( \IPS\Http\Url::internal("app=core&module=achievements&controller=rules&do=form&id={$rule->id}"), FALSE, $rule->extension()->ruleDescription( $rule ), TRUE, FALSE, FALSE, TRUE );
}
else
{
return \IPS\Member::loggedIn()->language()->addToStack('unknown');
}
},
'datetime' => function( $val )
{
return \IPS\DateTime::ts( $val );
}
);
\IPS\Output::i()->title = $badge->_title;
\IPS\Output::i()->output = $table;
}
/**
* Import dialog
*
* @return void
*/
public function exportForm()
{
$assignedBadges = \IPS\core\Achievements\Badge::getAssignedBadgeIds();
$where = NULL;
if ( \count( $assignedBadges ) )
{
$where = [ \IPS\Db::i()->in( '`id`', $assignedBadges, TRUE ) ];
}
$exportableBadges = \IPS\Db::i()->select( 'COUNT(*)', 'core_badges', $where )->first();
/* Display */
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'achievements' )->badgeExport( $exportableBadges );
}
/**
* Export ranks with images as an XML file (XML is better at potentially large values from raw image data)
*
* @return void
* @throws \Exception
*/
public function export()
{
$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 )
{
$langs[ \mb_substr( $row['word_key'], 12 ) ] = $row['word_default'];
}
$assignedBadges = \IPS\core\Achievements\Badge::getAssignedBadgeIds();
$where = NULL;
if ( \count( $assignedBadges ) )
{
$where = [ \IPS\Db::i()->in( '`id`', $assignedBadges, TRUE ) ];
}
/* Ranks */
foreach ( \IPS\Db::i()->select( '*', 'core_badges', $where ) as $badge )
{
try
{
$icon = \IPS\File::get( 'core_Badges', $badge['image'] );
$forXml = [
'manually_awarded' => $badge['manually_awarded'],
'title' => $langs[ $badge['id'] ],
'icon_name' => $icon->originalFilename,
'icon_data' => base64_encode( $icon->contents() )
];
$xml->addChild( 'badge', $forXml );
}
catch( \Exception $e ) { }
}
\IPS\Output::i()->sendOutput( $xml->asXML(), 200, 'application/xml', array( 'Content-Disposition' => \IPS\Output::getContentDisposition( 'attachment', "Achievement_Badges.xml" ) ) );
}
/**
* Import from upload
*
* @return void
*/
public function import()
{
\IPS\Session::i()->csrfCheck();
if ( !file_exists( \IPS\Request::i()->file ) or md5_file( \IPS\Request::i()->file ) !== \IPS\Request::i()->key )
{
\IPS\Output::i()->error( 'generic_error', '3C130/1', 500, '' );
}
try
{
\IPS\core\Achievements\Badge::importXml( \IPS\Request::i()->file, ( ! empty( \IPS\Request::i()->wipe ) ) );
}
catch( \Exception $e )
{
\IPS\Output::i()->error( $e->getMessage(), '2C163/1', 403, '' );
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=achievements&controller=badges' ), 'completed' );
}
/**
* Import dialog
*
* @return void
*/
public function importForm()
{
$form = new \IPS\Helpers\Form( 'form', 'acp_achievements_import' );
$form->add( new \IPS\Helpers\Form\YesNo( 'acp_achievements_import_option_badge_wipe', 0, FALSE, [], NULL, NULL, NULL, 'acp_achievements_import_option_rule_wipe' ) );
$form->add( new \IPS\Helpers\Form\Upload( 'acp_achievements_import_xml', NULL, FALSE, [ 'allowedFileTypes' => array( 'xml' ), 'temporary' => TRUE ], NULL, NULL, NULL, 'acp_achievements_import_xml' ) );
if ( $values = $form->values() )
{
/* Move it to a temporary location */
$tempFile = tempnam( \IPS\TEMP_DIRECTORY, 'IPS' );
move_uploaded_file( $values['acp_achievements_import_xml'], $tempFile );
/* Initate a redirector */
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=achievements&controller=badges&do=import' )->setQueryString( array('wipe' => $values['acp_achievements_import_option_badge_wipe'], 'file' => $tempFile, 'key' => md5_file( $tempFile )) )->csrf() );
}
/* Display */
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->block( \IPS\Member::loggedIn()->language()->addToStack('acp_achievements_import'), $form, FALSE );
}
}
@@ -0,0 +1,219 @@
<?php
/**
* @brief Achievement ranks
* @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 Apr 2013
*/
namespace IPS\core\modules\admin\achievements;
/* 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;
}
/**
* Achievement ranks
*/
class _ranks extends \IPS\Node\Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* @brief Cached counts
*/
public static $counts = [];
/**
* Node Class
*/
protected $nodeClass = 'IPS\core\Achievements\Rank';
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'ranks_manage' );
parent::execute();
}
/**
* Manage
*
* @return void
*/
protected function manage()
{
static::$counts = [];
$previousCount = NULL;
$allRanks = array_values( \IPS\core\Achievements\Rank::getStore() );
$totalRanks = \count( $allRanks );
for ( $i = 0; $i < $totalRanks; $i++ )
{
$where = [];
$where[] = [ 'achievements_points>=' . \intval( $allRanks[ $i ]->points ) ];
if ( isset( $allRanks[ $i + 1 ] ) )
{
$where[] = [ 'achievements_points<' . \intval( $allRanks[ $i + 1 ]->points ) ];
}
static::$counts[ $allRanks[ $i ]->id ] = \IPS\Db::i()->select( 'COUNT(*)', 'core_members', $where )->first();
}
if( $data = \IPS\core\Achievements\Rule::getRebuildProgress() )
{
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'achievements' )->rebuildProgress( $data, TRUE );
}
\IPS\Output::i()->sidebar['actions']['export'] = array(
'primary' => false,
'icon' => 'cloud-download',
'link' => \IPS\Http\Url::internal('app=core&module=achievements&controller=ranks&do=export'),
'data' => [],
'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=ranks&do=importForm'),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('acp_achievements_import') ),
'title' => 'acp_achievements_import',
);
return parent::manage();
}
/**
* Import dialog
*
* @return void
*/
public function importForm()
{
$form = new \IPS\Helpers\Form( 'form', 'acp_achievements_import' );
$form->add( new \IPS\Helpers\Form\Radio( 'acp_achievements_import_option', 'replace', FALSE, [ 'options' =>
[
'wipe' => 'acp_achievements_import_option_wipe',
'replace' => 'acp_achievements_import_option_replace'
],
'toggles' => [ 'wipe' => [ 'acp_achievements_import_option_wipe_sure' ] ]
] ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'acp_achievements_import_option_wipe_sure', 0, FALSE, [], NULL, NULL, NULL, 'acp_achievements_import_option_wipe_sure' ) );
$form->add( new \IPS\Helpers\Form\Upload( 'acp_achievements_import_xml', NULL, FALSE, [ 'allowedFileTypes' => array( 'xml' ), 'temporary' => TRUE ], NULL, NULL, NULL, 'acp_achievements_import_xml' ) );
if ( $values = $form->values() )
{
if ( $values['acp_achievements_import_option'] == 'wipe' and empty( $values['acp_achievements_import_option_wipe_sure'] ) )
{
$form->error = \IPS\Member::loggedIn()->language()->addToStack( 'acp_achievements_import_error' );
}
else
{
/* Move it to a temporary location */
$tempFile = tempnam( \IPS\TEMP_DIRECTORY, 'IPS' );
move_uploaded_file( $values['acp_achievements_import_xml'], $tempFile );
/* Initate a redirector */
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=achievements&controller=ranks&do=import' )->setQueryString( array('option' => $values['acp_achievements_import_option'], 'file' => $tempFile, 'key' => md5_file( $tempFile )) )->csrf() );
}
}
/* Display */
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->block( \IPS\Member::loggedIn()->language()->addToStack('acp_achievements_import'), $form, FALSE );
}
/**
* Import from upload
*
* @return void
*/
public function import()
{
\IPS\Session::i()->csrfCheck();
if ( !file_exists( \IPS\Request::i()->file ) or md5_file( \IPS\Request::i()->file ) !== \IPS\Request::i()->key )
{
\IPS\Output::i()->error( 'generic_error', '3C130/1', 500, '' );
}
try
{
\IPS\core\Achievements\Rank::importXml( \IPS\Request::i()->file, \IPS\Request::i()->option );
}
catch( \Exception $e )
{
\IPS\Output::i()->error( $e->getMessage(), '2C163/1', 403, '' );
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=achievements&controller=ranks' ), 'completed' );
}
/**
* Export ranks with images as an XML file (XML is better at potentially large values from raw image data)
*
* @return void
* @throws \Exception
*/
public function export()
{
$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 )
{
$langs[ \mb_substr( $row['word_key'], 17 ) ] = $row['word_default'];
}
/* Ranks */
foreach ( \IPS\Db::i()->select( '*', 'core_member_ranks') as $row )
{
$forXml = [
'posts' => $row['posts'],
'title' => isset( $langs[ $row['id'] ] ) ? $langs[ $row['id'] ] : NULL,
'points' => $row['points']
];
if ( $row['icon'] )
{
try
{
$icon = \IPS\File::get( 'core_Badges', $row['icon'] );
$forXml['icon_name'] = $icon->originalFilename;
$forXml['icon_data'] = base64_encode( $icon->contents() );
}
catch( \Exception $e ) { }
}
$xml->addChild( 'rank', $forXml );
}
\IPS\Output::i()->sendOutput( $xml->asXML(), 200, 'application/xml', array( 'Content-Disposition' => \IPS\Output::getContentDisposition( 'attachment', "Achievement_Ranks.xml" ) ) );
}
/**
* Fetch any additional HTML for this row
*
* @param object $node Node returned from $nodeClass::load()
* @return NULL|string
*/
public function _getRowHtml( $node )
{
$url = \IPS\Http\Url::internal("app=core&module=members&controller=members&advanced_search_submitted=1&members_achievements_points={$node->id}")->csrf();
return \IPS\Theme::i()->getTemplate('achievements')->memberCount( $url, static::$counts[ $node->id ] );
}
}
@@ -0,0 +1,291 @@
<?php
/**
* @brief Achievement rules
* @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 Feb 2021
*/
namespace IPS\core\modules\admin\achievements;
/* 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;
}
/**
* Achievement rules
*/
class _rules extends \IPS\Node\Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* Node Class
*/
protected $nodeClass = 'IPS\core\Achievements\Rule';
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'rules_manage' );
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'achievements/achievements.css', 'core', 'admin' ) );
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'admin_members.js', 'core' ) );
parent::execute();
}
/**
* Manage
*
* @return void
*/
protected function manage()
{
$baseUrl = \IPS\Http\Url::internal('app=core&module=achievements&controller=rules');
$perPage = 25;
$totalCount = \IPS\Db::i()->select( 'COUNT(*)', 'core_achievements_rules' )->first();
$totalPages = ceil( $totalCount / $perPage );
$page = \IPS\Request::i()->page ?? 1;
$query = \IPS\Db::i()->select( '*', 'core_achievements_rules', NULL, 'action, milestone ASC', [ ( $perPage * ( $page - 1 ) ), $perPage ] );
$rules = new \IPS\Patterns\ActiveRecordIterator( $query, 'IPS\core\Achievements\Rule' );
$pagination = $totalPages > 1 ? \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->pagination( $baseUrl, $totalPages, $page, $perPage ) : '';
if( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->json([
'rows' => \IPS\Theme::i()->getTemplate( 'achievements' )->rulesListRows( $rules ),
'pagination' => $pagination
]);
}
else
{
\IPS\Output::i()->sidebar['actions']['export'] = array(
'primary' => false,
'icon' => 'cloud-download',
'link' => \IPS\Http\Url::internal('app=core&module=achievements&controller=rules&do=export'),
'data' => [],
'title' => 'acp_achievements_export',
);
if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'achievements', 'rules_add' ) )
{
\IPS\Output::i()->sidebar['actions']['import'] = array(
'primary' => false,
'icon' => 'cloud-upload',
'link' => \IPS\Http\Url::internal('app=core&module=achievements&controller=rules&do=importForm'),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('acp_achievements_import') ),
'title' => 'acp_achievements_import',
);
}
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'achievements' )->rulesList( $rules, $pagination, $this->_getRootButtons() );
}
}
/**
* Get form
*
* @param \IPS\Helpers\Form $form The form as returned by _addEditForm()
* @return string
*/
protected function _showForm( \IPS\Helpers\Form $form )
{
\IPS\Output::i()->hiddenElements = array('acpHeader');
if ( \IPS\Request::i()->id )
{
\IPS\Dispatcher::i()->checkAcpPermission( 'rules_edit' );
try
{
$form->hiddenValues['rule_enabled'] = \IPS\core\Achievements\Rule::load( \IPS\Request::i()->id )->enabled;
}
catch( \Exception $e ) { }
}
else
{
\IPS\Dispatcher::i()->checkAcpPermission( 'rules_add' );
}
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_achievements_rules');
return $form->customTemplate( [ \IPS\Theme::i()->getTemplate( 'achievements' ), 'rulesForm' ] );
}
/**
* Toggle enabled status
*
* @return void
*/
protected function toggleEnabled()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'rules_edit' );
\IPS\Session::i()->csrfCheck();
try
{
$rule = \IPS\core\Achievements\Rule::load( \IPS\Request::i()->id );
$rule->enabled = (int) \IPS\Request::i()->enable;
$rule->save();
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=achievements&controller=rules' ), 'updated' );
}
catch( \Exception $e )
{
\IPS\Output::i()->error( 'node_error', '2T353/3', 404, '' ); /* @todo error code */
}
}
/**
* Export rules with badges as an XML file (XML is better at potentially large values from raw image data)
*
* @return void
* @throws \Exception
*/
public function export()
{
$xml = \IPS\Xml\SimpleXML::create('rules');
$badgeMap = [];
$langs = [];
foreach( \IPS\Db::i()->select( 'word_key, word_default', 'core_sys_lang_words', [ 'lang_id=? AND (word_key LIKE \'core_award_subject_badge_%\' OR word_key LIKE \'core_award_other_badge_%\')', \IPS\Member::loggedIn()->language()->id ] ) as $row )
{
$langs[ $row['word_key'] ] = $row['word_default'];
}
/* Rules */
foreach ( \IPS\Db::i()->select( '*', 'core_achievements_rules') as $row )
{
$forXml = [];
foreach( $row as $k => $v )
{
if ( $k !== 'id' )
{
if ( $k == 'badge_subject' and $v )
{
if ( !isset( $badgeMap[$row['badge_subject']] ) )
{
$badgeMap[$row['badge_subject']] = uniqid();
}
$v = $badgeMap[$row['badge_subject']];
}
if ( $k == 'badge_other' and $v )
{
if ( !isset( $badgeMap[$row['badge_other']] ) )
{
$badgeMap[$row['badge_other']] = uniqid();
}
$v = $badgeMap[$row['badge_other']];
}
$forXml[$k] = $v;
}
}
$forXml['award_subject_lang'] = $langs['core_award_subject_badge_' . $row['id']] ?? NULL;
$forXml['award_other_lang'] = $langs['core_award_other_badge_' . $row['id']] ?? NULL;
$xml->addChild( 'rule', $forXml );
}
/* Badges */
if ( \count( $badgeMap ) )
{
$langs = [];
foreach( \IPS\Db::i()->select('word_key, word_default', 'core_sys_lang_words', [ \IPS\Db::i()->like( 'word_key', 'core_badges_') ] ) as $row )
{
$langs[ \mb_substr( $row['word_key'], 12 ) ] = $row['word_default'];
}
foreach( \IPS\Db::i()->select('*', 'core_badges', [ \IPS\Db::i()->in( '`id`', array_keys( $badgeMap ) ) ] ) as $badge )
{
try
{
$icon = \IPS\File::get( 'core_Badges', $badge['image'] );
$xml->addChild( 'badge', [
'manually_awarded' => $badge['manually_awarded'],
'id' => $badgeMap[ $badge['id'] ],
'title' => $langs[ $badge['id'] ],
'icon_name' => $icon->originalFilename,
'icon_data' => base64_encode( $icon->contents() )
] );
}
catch( \Exception $e ) { }
}
}
\IPS\Output::i()->sendOutput( $xml->asXML(), 200, 'application/xml', array( 'Content-Disposition' => \IPS\Output::getContentDisposition( 'attachment', "Achievement_Rules.xml" ) ) );
}
/**
* Import dialog
*
* @return void
*/
public function importForm()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'rules_add' );
$form = new \IPS\Helpers\Form( 'form', 'acp_achievements_import' );
$form->addMessage( \IPS\Member::loggedIn()->language()->addToStack( 'acp_achievements_import_rules_message'), 'ipsMessage ipsMessage_info');
$form->add( new \IPS\Helpers\Form\YesNo( 'acp_achievements_import_option_rule_wipe', 0, FALSE, [], NULL, NULL, NULL, 'acp_achievements_import_option_rule_wipe' ) );
$form->add( new \IPS\Helpers\Form\Upload( 'acp_achievements_import_xml', NULL, FALSE, [ 'allowedFileTypes' => array( 'xml' ), 'temporary' => TRUE ], NULL, NULL, NULL, 'acp_achievements_import_xml' ) );
if ( $values = $form->values() )
{
/* Move it to a temporary location */
$tempFile = tempnam( \IPS\TEMP_DIRECTORY, 'IPS' );
move_uploaded_file( $values['acp_achievements_import_xml'], $tempFile );
/* Initate a redirector */
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=achievements&controller=rules&do=import' )->setQueryString( array('wipe' => $values['acp_achievements_import_option_rule_wipe'], 'file' => $tempFile, 'key' => md5_file( $tempFile )) )->csrf() );
}
/* Display */
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->block( \IPS\Member::loggedIn()->language()->addToStack('acp_achievements_import'), $form, FALSE );
}
/**
* Import from upload
*
* @return void
*/
public function import()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'rules_add' );
\IPS\Session::i()->csrfCheck();
if ( !file_exists( \IPS\Request::i()->file ) or md5_file( \IPS\Request::i()->file ) !== \IPS\Request::i()->key )
{
\IPS\Output::i()->error( 'generic_error', '3C130/1', 500, '' );
}
try
{
\IPS\core\Achievements\Rule::importXml( \IPS\Request::i()->file, ( ! empty( \IPS\Request::i()->wipe ) ) );
}
catch( \Exception $e )
{
\IPS\Output::i()->error( $e->getMessage(), '2C163/1', 403, '' );
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=achievements&controller=rules' ), 'completed' );
}
}
@@ -0,0 +1,119 @@
<?php
/**
* @brief Achievement 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 01 Mar 2021
*/
namespace IPS\core\modules\admin\achievements;
/* 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;
}
/**
* Achievement settings
*/
class _settings extends \IPS\Dispatcher\Controller
{
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'settings_manage' );
parent::execute();
}
/**
* Manage Settings
*
* @return void
*/
protected function manage()
{
$form = new \IPS\Helpers\Form;
$form->add( new \IPS\Helpers\Form\YesNo( 'achievements_enabled', \IPS\Settings::i()->achievements_enabled, FALSE, [ 'togglesOn' => [ 'rare_badge_percent', 'prune_points_log', 'rules_exclude_groups', 'achievements_recognize_max_per_user_day' ] ] ) );
$form->add( new \IPS\Helpers\Form\Number( 'rare_badge_percent', \IPS\Settings::i()->rare_badge_percent, FALSE, [ 'decimals' => 1, 'unlimited' => 0, 'unlimitedLang' => 'never' ], NULL, \IPS\Member::loggedIn()->language()->addToStack('rare_badge_percent_prefix'), \IPS\Member::loggedIn()->language()->addToStack('rare_badge_percent_suffix'), 'rare_badge_percent' ) );
$form->add( new \IPS\Helpers\Form\Interval( 'prune_points_log', \IPS\Settings::i()->prune_points_log, FALSE, [ 'valueAs' => \IPS\Helpers\Form\Interval::DAYS, 'unlimited' => 0, 'unlimitedLang' => 'never' ], NULL, \IPS\Member::loggedIn()->language()->addToStack('after'), NULL, 'prune_points_log' ) );
$groups = array_combine( array_keys( \IPS\Member\Group::groups( TRUE, FALSE ) ), array_map( function( $_group ) { return (string) $_group; }, \IPS\Member\Group::groups( TRUE, FALSE ) ) );
$selectedGroups = json_decode( \IPS\Settings::i()->rules_exclude_groups, TRUE );
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'rules_exclude_groups', $selectedGroups, FALSE, array( 'options' => $groups, 'multiple' => true ), NULL, NULL, NULL, 'rules_exclude_groups' ) );
$form->add( new \IPS\Helpers\Form\Number( 'achievements_recognize_max_per_user_day', \IPS\Settings::i()->achievements_recognize_max_per_user_day, FALSE, [ 'unlimited' => -1 ], NULL, \IPS\Member::loggedIn()->language()->addToStack('achievements_recognize_max_per_user_day_prefix'), \IPS\Member::loggedIn()->language()->addToStack('achievements_recognize_max_per_user_day_suffix'), 'achievements_recognize_max_per_user_day' ) );
if ( $values = $form->values() )
{
$values['rules_exclude_groups'] = json_encode( $values['rules_exclude_groups'] );
$form->saveAsSettings( $values );
\IPS\Session::i()->log( 'acplog__achievement_settings' );
}
if ( \IPS\Settings::i()->achievements_enabled )
{
\IPS\Output::i()->sidebar['actions']['rebuild'] = array(
'primary' => true,
'icon' => 'plus',
'link' => \IPS\Http\Url::internal( 'app=core&module=achievements&controller=settings&do=rebuildForm' )->csrf(),
'data' => array('ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack( 'acp_rebuild_achievements' )),
'title' => 'acp_rebuild_achievements',
);
}
if( $data = \IPS\core\Achievements\Rule::getRebuildProgress() )
{
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'achievements' )->rebuildProgress( $data, TRUE );
}
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('achievements_settings');
\IPS\Output::i()->output .= $form;
}
/**
* Rebuild Members' Achievements
*
* @return void
*/
protected function rebuildForm()
{
if ( ! \IPS\Settings::i()->achievements_enabled )
{
\IPS\Output::i()->error( 'achievements_not_enabled', '1C421/1', 403, '' );
}
$form = new \IPS\Helpers\Form( 'rebuild_form', 'acp_rebuild_achievements_rebuild');
$form->addMessage('acp_rebuild_achievements_blurb', 'ipsMessage ipsMessage_info');
$form->add( new \IPS\Helpers\Form\Checkbox( 'acp_rebuild_achievements_checkbox', FALSE, TRUE ) );
$form->add( new \IPS\Helpers\Form\Date( 'acp_rebuild_achievements_time', 0, TRUE, [ 'unlimited' => 0, 'unlimitedLang' => 'acp_rebuild_achievements_time_unlimited' ] ) );
if ( $values = $form->values() )
{
if ( ! $values['acp_rebuild_achievements_checkbox'] )
{
$form->error = \IPS\Member::loggedIn()->language()->addToStack('acp_achievements_rebuild_not_checked');
}
else
{
\IPS\core\Achievements\Rule::rebuildAllAchievements( $values['acp_rebuild_achievements_time'] ?: NULL );
\IPS\Settings::i()->changeValues( array('achievements_rebuilding' => 1) );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=achievements&controller=settings' ) );
}
}
\IPS\Output::i()->bypassCsrfKeyCheck = TRUE;
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('acp_rebuild_achievements');
\IPS\Output::i()->output = $form;
}
}
@@ -26,6 +26,11 @@ class _communityactivity extends \IPS\Dispatcher\Controller
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* @brief Allow MySQL RW separation for efficiency
*/
public static $allowRWSeparation = TRUE;
/**
* Execute
@@ -26,6 +26,11 @@ class _emailstats extends \IPS\Dispatcher\Controller
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* @brief Allow MySQL RW separation for efficiency
*/
public static $allowRWSeparation = TRUE;
/**
* Execute
@@ -201,7 +206,7 @@ class _emailstats extends \IPS\Dispatcher\Controller
}
catch( \UnderflowException $e ){}
$results[ $_date ][ $lang ] = $row['value_1'];
$results[ $_date ][ $lang ] += (int) $row['value_1'];
}
return $results;
@@ -26,6 +26,11 @@ class _keywords extends \IPS\Dispatcher\Controller
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* @brief Allow MySQL RW separation for efficiency
*/
public static $allowRWSeparation = TRUE;
/**
* Execute
@@ -22,11 +22,21 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
*/
class _overview extends \IPS\core\modules\admin\stats\overview
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* @brief Template group to use to output
*/
protected $templateGroup = 'activitystats';
/**
* @brief Allow MySQL RW separation for efficiency
*/
public static $allowRWSeparation = TRUE;
/**
* Create the general page layout, but we will load the individual cells via AJAX to ensure there are no performance concerns loading the page
*
@@ -26,6 +26,11 @@ class _reactions extends \IPS\Dispatcher\Controller
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* @brief Allow MySQL RW separation for efficiency
*/
public static $allowRWSeparation = TRUE;
/**
* Execute
@@ -27,6 +27,16 @@ class _search extends \IPS\Dispatcher\Controller
*/
protected $defaultLimit = 25;
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* @brief Allow MySQL RW separation for efficiency
*/
public static $allowRWSeparation = TRUE;
/**
* Execute
*
@@ -150,7 +160,7 @@ class _search extends \IPS\Dispatcher\Controller
$where[] = \IPS\Db::i()->like( 'value_4', $term, TRUE, TRUE, TRUE );
}
foreach( \IPS\Db::i()->select( 'value_4, COUNT(*) as total', 'core_statistics', $where, 'total DESC', $this->defaultLimit, 'value_4' ) as $searchedValue )
foreach( \IPS\Db::i()->select( 'SQL_BIG_RESULT value_4, COUNT(*) as total', 'core_statistics', $where, 'total DESC', $this->defaultLimit, 'value_4' ) as $searchedValue )
{
$this->_topSearchTerms[ $term ][] = $searchedValue['value_4'];
}
@@ -26,6 +26,11 @@ class _tags extends \IPS\Dispatcher\Controller
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* @brief Allow MySQL RW separation for efficiency
*/
public static $allowRWSeparation = TRUE;
/**
* Execute
@@ -69,6 +74,9 @@ class _tags extends \IPS\Dispatcher\Controller
{
$where[] = array( "tag_added<?", $chart->end->getTimestamp() );
}
/* Only get visible tags */
$where[] = array( 'tag_aai_lookup IN(?)', \IPS\Db::i()->select( 'tag_perm_aai_lookup', 'core_tags_perms', [ 'tag_perm_visible=1' ] ) );
foreach( \IPS\Db::i()->select( 'tag_text', 'core_tags', $where, NULL, NULL, array( 'tag_text' ) ) as $tag )
{
@@ -76,7 +76,7 @@ class _apiReference extends \IPS\Dispatcher\Controller
$params = array_merge( $params, $endpoints[ $selected ]['details']['apiparam'] );
}
foreach( $params as $data )
foreach( $params as $index => $data )
{
if ( mb_strpos( $data[0], '|' ) === FALSE AND !\in_array( $data[0], array( 'int', 'string', 'float', 'datetime', 'bool', 'object', 'array' ) ) )
{
@@ -84,12 +84,26 @@ class _apiReference extends \IPS\Dispatcher\Controller
{
if ( !\in_array( mb_substr( $data[0], 1, -1 ), array( 'int', 'string', 'float', 'datetime', 'bool', 'object', 'array' ) ) )
{
$additionalClassesToReference = array_merge( $additionalClassesToReference, $this->_getAdditionalClasses( mb_substr( $data[0], 1, -1 ) ) );
if( $returned = $this->_getAdditionalClasses( mb_substr( $data[0], 1, -1 ) ) )
{
$additionalClassesToReference = array_merge( $additionalClassesToReference, $returned );
}
elseif( $returned === NULL )
{
unset( $params[ $index ] );
}
}
}
else
{
$additionalClassesToReference = array_merge( $additionalClassesToReference, $this->_getAdditionalClasses( $data[0] ) );
if( $returned = $this->_getAdditionalClasses( $data[0] ) )
{
$additionalClassesToReference = array_merge( $additionalClassesToReference, $returned );
}
elseif( $returned === NULL )
{
unset( $params[ $index ] );
}
}
}
}
@@ -119,7 +133,7 @@ class _apiReference extends \IPS\Dispatcher\Controller
{
if ( isset( $endpoints[ $selected ]['details']['apiresponse'] ) )
{
foreach ( $endpoints[ $selected ]['details']['apiresponse'] as $data )
foreach ( $endpoints[ $selected ]['details']['apiresponse'] as $index => $data )
{
if ( mb_strpos( $data[0], '|' ) === FALSE AND !\in_array( $data[0], array( 'int', 'string', 'float', 'datetime', 'bool', 'object', 'array' ) ) )
{
@@ -127,12 +141,26 @@ class _apiReference extends \IPS\Dispatcher\Controller
{
if ( !\in_array( mb_substr( $data[0], 1, -1 ), array( 'int', 'string', 'float', 'datetime', 'bool', 'object', 'array' ) ) )
{
$additionalClassesToReference = array_merge( $additionalClassesToReference, $this->_getAdditionalClasses( mb_substr( $data[0], 1, -1 ) ) );
if( $returned = $this->_getAdditionalClasses( mb_substr( $data[0], 1, -1 ) ) )
{
$additionalClassesToReference = array_merge( $additionalClassesToReference, $returned );
}
elseif( $returned === NULL )
{
unset( $endpoints[ $selected ]['details']['apiresponse'][ $index ] );
}
}
}
else
{
$additionalClassesToReference = array_merge( $additionalClassesToReference, $this->_getAdditionalClasses( $data[0] ) );
if( $returned = $this->_getAdditionalClasses( $data[0] ) )
{
$additionalClassesToReference = array_merge( $additionalClassesToReference, $returned );
}
elseif( $returned === NULL )
{
unset( $endpoints[ $selected ]['details']['apiresponse'][ $index ] );
}
}
}
}
@@ -191,10 +219,15 @@ class _apiReference extends \IPS\Dispatcher\Controller
*
* @param string $class The classname
* @param bool $exclude If FALSE, will include this class itself in the return array
* @return array
* @return array|NULL
*/
protected function _getAdditionalClasses( $class, $exclude=FALSE )
{
if( !class_exists( $class ) )
{
return NULL;
}
$return = $exclude ? array() : array( $class => $class );
$reflection = new \ReflectionMethod( $class, 'apiOutput' );
$decoded = \IPS\Api\Controller::decodeDocblock( $reflection->getDocComment() );
@@ -206,12 +239,18 @@ class _apiReference extends \IPS\Dispatcher\Controller
{
if ( !\in_array( mb_substr( $response[0], 1, -1 ), $return ) and !\in_array( mb_substr( $response[0], 1, -1 ), array( 'int', 'string', 'float', 'datetime', 'bool', 'object', 'array' ) ) )
{
$return = array_merge( $return, $this->_getAdditionalClasses( mb_substr( $response[0], 1, -1 ) ) );
if( $returned = $this->_getAdditionalClasses( mb_substr( $response[0], 1, -1 ) ) )
{
$return = array_merge( $return, $returned );
}
}
}
elseif ( !\in_array( $response[0], $return ) )
{
$return = array_merge( $return, $this->_getAdditionalClasses( $response[0] ) );
if( $returned = $this->_getAdditionalClasses( $response[0] ) )
{
$return = array_merge( $return, $returned );
}
}
}
}
@@ -58,7 +58,14 @@ class _applications extends \IPS\Node\Controller
/* Create the basic tree */
if ( !\IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('forms')->blurb( 'applications_blurb' );
if( \IPS\IPS::canManageResources() )
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('forms')->blurb( 'applications_blurb' );
}
else
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('forms')->blurb( 'applications_blurb_no_upload' );
}
}
parent::manage();
@@ -120,26 +127,38 @@ class _applications extends \IPS\Node\Controller
{
foreach ( $uninstalled as $k => $app )
{
$rows[ $k ] = \IPS\Theme::i()->getTemplate( 'trees' )->row( $baseUrl, $k, $app['title'], FALSE, array(
'add' => array(
'icon' => 'plus-circle',
'title' => 'install',
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=applications&appKey={$k}&do=install" )->csrf(),
)
) );
$buttons = array();
if( \IPS\IPS::canManageResources() )
{
$buttons = array(
'add' => array(
'icon' => 'plus-circle',
'title' => 'install',
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=applications&appKey={$k}&do=install" )->csrf(),
)
);
}
$rows[ $k ] = \IPS\Theme::i()->getTemplate( 'trees' )->row( $baseUrl, $k, $app['title'], FALSE, $buttons );
}
}
return $rows;
},
function( $key, $root=FALSE ) use ( $uninstalled, $baseUrl )
{
return \IPS\Theme::i()->getTemplate( 'trees' )->row( $baseUrl, $key, $uninstalled[ $key ]['title'], FALSE, array(
'add' => array(
'icon' => 'plus-circle',
'title' => 'install',
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=applications&appKey={$key}&do=install" )->csrf(),
)
), '', NULL, NULL, $root );
$buttons = array();
if( \IPS\IPS::canManageResources() )
{
$buttons = array(
'add' => array(
'icon' => 'plus-circle',
'title' => 'install',
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=applications&appKey={$key}&do=install" )->csrf(),
)
);
}
return \IPS\Theme::i()->getTemplate( 'trees' )->row( $baseUrl, $key, $uninstalled[ $key ]['title'], FALSE, $buttons, '', NULL, NULL, $root );
},
function() { return 0; },
function() { return array(); },
@@ -185,14 +204,16 @@ class _applications extends \IPS\Node\Controller
{
foreach ( $legacyApps as $k => $app )
{
$buttons = array(
'upgrade' => array(
'icon' => 'upload',
'title' => 'upload_new_version',
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=applications&appKey={$k}&do=upload" ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('upload_new_version') )
)
);
$buttons = array();
if( \IPS\IPS::canManageResources() )
{
$buttons['upgrade'] = array(
'icon' => 'upload',
'title' => 'upload_new_version',
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=applications&appKey={$k}&do=upload" ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack( 'upload_new_version' ) )
);
}
if( \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'applications', 'app_delete' ) )
{
@@ -212,14 +233,16 @@ class _applications extends \IPS\Node\Controller
},
function( $key, $root=FALSE ) use ( $legacyApps, $baseUrl )
{
$buttons = array(
'upgrade' => array(
'icon' => 'upload',
'title' => 'upload_new_version',
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=applications&appKey={$key}&do=upload" ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('upload_new_version') )
)
);
$buttons = array();
if( \IPS\IPS::canManageResources() )
{
$buttons['upgrade'] = array(
'icon' => 'upload',
'title' => 'upload_new_version',
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=applications&appKey={$key}&do=upload" ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack( 'upload_new_version' ) )
);
}
if( \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'applications', 'app_delete' ) )
{
@@ -460,11 +483,12 @@ class _applications extends \IPS\Node\Controller
\IPS\Member::loggedIn()->language()->words['app_enabled_desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'app_enabled_desc_no_writes' );
}
$form->add( new \IPS\Helpers\Form\Select( 'app_disabled_groups', ( $node->disabled_groups == '*' or $node->disabled_groups === NULL ) ? '*' : explode( ',', $node->disabled_groups ), FALSE, array(
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'app_disabled_groups', ( $node->disabled_groups == '*' or $node->disabled_groups === NULL ) ? '*' : explode( ',', $node->disabled_groups ), FALSE, array(
'options' => array_combine( array_keys( \IPS\Member\Group::groups() ), array_map( function( $_group ) { return (string) $_group; }, \IPS\Member\Group::groups() ) ),
'multiple' => true,
'unlimited' => '*',
'unlimitedLang' => 'all'
'unlimitedLang' => 'all',
'impliedUnlimited' => TRUE
), NULL, NULL, NULL, 'app_disabled_groups' ) );
$form->add( new \IPS\Helpers\Form\Editor( 'app_disabled_message', $node->disabled_message, FALSE, array( 'app' => 'core', 'key' => 'Admin', 'autoSaveKey' => $node->_key . 'app_disabled_message', 'attachIds' => array( $node->id, NULL, 'appdisabled' ) ), NULL, NULL, NULL, 'app_disabled_message_editor' ) );
@@ -533,13 +557,23 @@ class _applications extends \IPS\Node\Controller
$form = new \IPS\Helpers\Form;
$form->add( new \IPS\Helpers\Form\Radio( 'developer_build_type', 'rebuild', TRUE, $options ) );
$form->add( new \IPS\Helpers\Form\Text( 'versions_human', $defaults['human'], NULL, array(), NULL, NULL, NULL, 'versions_human' ) );
$form->add( new \IPS\Helpers\Form\Text( 'versions_human', $defaults['human'], NULL, array(), function( $val )
{
if ( !preg_match( '/^([0-9]+\.[0-9]+\.[0-9]+)/', $val ) )
{
throw new \DomainException( 'versions_human_bad' );
}
}, NULL, NULL, 'versions_human' ) );
$form->add( new \IPS\Helpers\Form\Text( 'versions_long', $defaults['long'], NULL, array(), function( $val ) use ( $json )
{
if ( !preg_match( '/^\d*$/', $val ) )
{
throw new \DomainException( 'form_number_bad' );
}
if( $val < 10000 )
{
throw new \DomainException( 'versions_long_too_low' );
}
if( isset( $json[ $val ] ) )
{
throw new \DomainException( 'versions_long_exists' );
@@ -568,6 +602,12 @@ class _applications extends \IPS\Node\Controller
{
foreach ( \IPS\Application::applications() as $application )
{
/* Skip building Marketplace applications */
if( $application->marketplace_id )
{
continue;
}
if ( $values['developer_build_type'] === 'new' )
{
$application->assignNewVersion( $values['versions_long'], $values['versions_human'] );
@@ -643,6 +683,12 @@ class _applications extends \IPS\Node\Controller
public function download()
{
$application = \IPS\Application::load( \IPS\Request::i()->appKey );
/* Don't allow downloads of Marketplace resources */
if( $application->marketplace_id )
{
\IPS\Output::i()->error( 'application_cannot_build_marketplace', '2C133/Y', 403, '' );
}
$form = $this->_buildForm( $application->directory, TRUE );
if ( $values = $form->values() )
@@ -682,7 +728,7 @@ class _applications extends \IPS\Node\Controller
unset($download);
\Phar::unlinkArchive($pharPath);
\IPS\Output::i()->sendOutput( $output, 200, 'application/tar', array( 'Content-Disposition' => \IPS\Output::getContentDisposition( 'attachment', $application->directory . '.tar' ) ), FALSE, FALSE, FALSE );
\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 );
}
\IPS\Output::i()->output = $form;
@@ -726,9 +772,9 @@ class _applications extends \IPS\Node\Controller
}
/* Get the application data to update the application record */
if( file_exists( \IPS\ROOT_PATH . '/applications/' . \IPS\Request::i()->appKey . '/data/application.json' ) )
if( file_exists( \IPS\Application::getRootPath( \IPS\Request::i()->appKey ) . '/applications/' . \IPS\Request::i()->appKey . '/data/application.json' ) )
{
$application = json_decode( file_get_contents( \IPS\ROOT_PATH . '/applications/' . $app->directory . '/data/application.json' ), TRUE );
$application = json_decode( file_get_contents( \IPS\Application::getRootPath( $app->directory ) . '/applications/' . $app->directory . '/data/application.json' ), TRUE );
//\IPS\Lang::saveCustom( $app->directory, "__app_{$app->directory}", $application['application_title'] );
@@ -921,10 +967,10 @@ class _applications extends \IPS\Node\Controller
$_SESSION['cmsConflictKey'] = '';
try
{
$result = \IPS\cms\Templates::importTemplatesFromXml( \IPS\ROOT_PATH . '/applications/' . \IPS\Application::load( $data['key'] )->directory . '/data/cmsTemplates.xml' );
$result = \IPS\cms\Templates::importUserTemplateXml( \IPS\Application::getRootPath( $data['key'] ) . '/applications/' . \IPS\Application::load( $data['key'] )->directory . '/data/cmsTemplates.xml' );
if( $result instanceof \IPS\Http\Url\Internal )
{
$_SESSION['cmsConflictKey'] = $result->setQueryString( [ 'app' => \IPS\Application::load( $data['key'] )->directory, 'lang' => 'updated' ] );
$_SESSION['cmsConflictKey'] = $result->setQueryString( [ 'application' => \IPS\Application::load( $data['key'] )->directory, 'lang' => 'updated' ] );
}
}
catch( \Throwable $e )
@@ -1174,10 +1220,10 @@ class _applications extends \IPS\Node\Controller
$_SESSION['cmsConflictKey'] = '';
try
{
$result = \IPS\cms\Templates::importTemplatesFromXml( \IPS\Application::getRootPath( $data['key'] ) . '/applications/' . $data['key'] . '/data/cmsTemplates.xml' );
$result = \IPS\cms\Templates::importUserTemplateXml( \IPS\Application::getRootPath( $data['key'] ) . '/applications/' . $data['key'] . '/data/cmsTemplates.xml' );
if( $result instanceof \IPS\Http\Url\Internal )
{
$_SESSION['cmsConflictKey'] = $result->setQueryString( [ 'app' => $data['key'], 'lang' => 'installed' ] );
$_SESSION['cmsConflictKey'] = $result->setQueryString( [ 'application' => $data['key'], 'lang' => 'installed' ] );
}
}
catch( \Throwable $e )
@@ -1507,7 +1553,7 @@ class _applications extends \IPS\Node\Controller
\IPS\Output::i()->error( 'no_app_to_update', '2C133/C', 403, '' );
}
if( !is_writable( \IPS\ROOT_PATH . "/applications/" . $app->directory ) )
if( !\IPS\CIC2 AND !is_writable( \IPS\ROOT_PATH . "/applications/" . $app->directory ) )
{
\IPS\Output::i()->error( \IPS\Member::loggedIn()->language()->addToStack( "app_specific_dir_nowrite", FALSE, array( 'sprintf' => $app->directory ) ), '4C133/D', 500, '' );
}
@@ -35,6 +35,10 @@ class _developer extends \IPS\Dispatcher\Controller
*/
public function execute( $command='do' )
{
/* This controller can only be accessed in developer mode, so we can bypass the check global */
\IPS\Output::i()->bypassCsrfKeyCheck = true;
/* Are we in developer mode? */
if( !\IPS\IN_DEV )
{
@@ -130,17 +134,20 @@ class _developer extends \IPS\Dispatcher\Controller
if ( $activeTabContents )
{
/* Add the build and download button */
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'admin_system.js', 'core', 'admin' ) );
\IPS\Output::i()->sidebar['actions']['build'] = array(
'icon' => 'download',
'title' => 'download',
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=applications&appKey={$this->application->directory}&do=download" ),
'data' => array(
'controller' => 'system.buildApp',
'downloadURL' => \IPS\Http\Url::internal( "app=core&module=applications&controller=applications&appKey={$this->application->directory}&do=download&type=download" ),
'buildURL' => \IPS\Http\Url::internal( "app=core&module=applications&controller=applications&appKey={$this->application->directory}&do=download&type=build" ),
)
);
if( !$this->application->marketplace_id )
{
\IPS\Output::i()->jsFiles = array_merge(\IPS\Output::i()->jsFiles, \IPS\Output::i()->js('admin_system.js', 'core', 'admin'));
\IPS\Output::i()->sidebar['actions']['build'] = array(
'icon' => 'download',
'title' => 'download',
'link' => \IPS\Http\Url::internal("app=core&module=applications&controller=applications&appKey={$this->application->directory}&do=download"),
'data' => array(
'controller' => 'system.buildApp',
'downloadURL' => \IPS\Http\Url::internal("app=core&module=applications&controller=applications&appKey={$this->application->directory}&do=download&type=download"),
'buildURL' => \IPS\Http\Url::internal("app=core&module=applications&controller=applications&appKey={$this->application->directory}&do=download&type=build"),
)
);
}
\IPS\Output::i()->title = $this->application->_title;
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->tabs( $tabs, $this->activeTab, $activeTabContents, \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}" ) );
@@ -1842,10 +1849,30 @@ class _developer extends \IPS\Dispatcher\Controller
elseif ( $activeTab === 'inserts' )
{
$keys = [];
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" ) );
$output->langPrefix = "&zwnj;";
$output->limit = 150;
foreach( $keys as $key )
{
$output->parsers[ $key ] = function( $val )
{
if ( mb_strlen( $val ) > 100 )
{
return mb_substr( $val, 0, 97 ) . '...';
}
return $val;
};
}
$output->rootButtons = array(
'add' => array(
'icon' => 'plus',
@@ -1994,6 +2021,15 @@ class _developer extends \IPS\Dispatcher\Controller
$values[ $k ] = NULL;
}
}
/* Check default value is a number, where it should be a number */
if( !$values['database_column_allow_null'] AND empty( $values['database_column_default'] ) )
{
if( \in_array( $values['database_column_type'], array( 'TINYINT', 'SMALLINT', 'MEDIUMINT', 'INT', 'INTEGER', 'BIGINT', 'REAL', 'DOUBLE', 'FLOAT', 'DECIMAL', 'NUMERIC' ) ) )
{
$values['database_column_default'] = 0;
}
}
/* Get a column definition */
$save = array();
@@ -2245,16 +2281,16 @@ class _developer extends \IPS\Dispatcher\Controller
{
\IPS\Db::i()->addIndex( $definition['name'], $save );
$this->_writeQueries( 'working', $this->_addQueryToJson( $this->_getQueries( 'working' ), array( 'method' => 'addIndex', 'params' => array( $definition['name'], $save ) ) ) );
if ( $index['name'] != $save['name'] )
{
unset( $schema[ $definition['name'] ]['indexes'][ $index['name'] ] );
}
}
else
{
\IPS\Db::i()->changeIndex( $definition['name'], $index['name'], $save );
$this->_writeQueries( 'working', $this->_addQueryToJson( $this->_getQueries( 'working' ), array( 'method' => 'changeIndex', 'params' => array( $definition['name'], $index['name'], $save ) ) ) );
if ( $index['name'] != $save['name'] )
{
unset( $schema[ $definition['name'] ]['indexes'][ $index['name'] ] );
}
}
$schema[ $definition['name'] ]['indexes'][ $save['name'] ] = $save;
@@ -3103,7 +3139,7 @@ class _developer extends \IPS\Dispatcher\Controller
$queries = $this->_getQueries( $long );
$order = 1;
$rows = array();
foreach ( $queries as $qid => $data )
foreach ( ( $queries ?? [] ) as $qid => $data )
{
$params = array();
if ( isset( $data['params'] ) and \is_array( $data['params'] ) )
@@ -3121,7 +3157,7 @@ class _developer extends \IPS\Dispatcher\Controller
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=deleteVersionQuery&version={$long}&query={$qid}" ),
'data' => array( 'delete' => '' )
)
), NULL, NULL, $order, FALSE, NULL, NULL, NULL, TRUE );
), NULL, NULL, NULL, FALSE, NULL, NULL, NULL, TRUE, FALSE, FALSE, FALSE );
$order++;
}
@@ -3147,7 +3183,13 @@ class _developer extends \IPS\Dispatcher\Controller
/* Create New */
case 'new':
$form->addMessage( 'versions_add_information' );
$form->add( new \IPS\Helpers\Form\Text( 'versions_human', NULL, TRUE, array( 'placeholder' => '1.0.0' ) ) );
$form->add( new \IPS\Helpers\Form\Text( 'versions_human', NULL, TRUE, array( 'placeholder' => '1.0.0' ), function( $val )
{
if ( !preg_match( '/^([0-9]+\.[0-9]+\.[0-9]+)/', $val ) )
{
throw new \DomainException( 'versions_human_bad' );
}
} ) );
$form->add( new \IPS\Helpers\Form\Text( 'versions_long', NULL, TRUE, array( 'placeholder' => '10000' ), function( $val ) use ( $json )
{
if ( !preg_match( '/^\d*$/', $val ) )
@@ -206,7 +206,7 @@ class _menu extends \IPS\Dispatcher\Controller
'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\Select( 'menu_manager_access', $existing ? ( $existing['permissions'] == '*' ? '*' : explode( ',', $existing['permissions'] ) ) : '*', NULL, array( 'multiple' => TRUE, 'options' => $groups, 'unlimited' => '*', 'unlimitedLang' => 'everyone' ), NULL, NULL, NULL, 'menu_manager_access' ) );
$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;
@@ -472,7 +472,7 @@ class _oauth extends \IPS\Node\Controller
\IPS\Dispatcher::i()->checkAcpPermission( 'oauth_tokens' );
\IPS\Session::i()->csrfCheck();
\IPS\Db::i()->delete( 'core_oauth_server_access_tokens', array( 'client_id=? AND access_token=?', \IPS\Request::i()->client_id, \IPS\Request::i()->token ) );
\IPS\Db::i()->update( 'core_oauth_server_access_tokens', array( 'status' => 'revoked' ), array( 'client_id=? AND access_token=?', \IPS\Request::i()->client_id, \IPS\Request::i()->token ) );
\IPS\Session::i()->log( 'acplogs__oauth_revoke_token', array( 'core_oauth_client_' . \IPS\Request::i()->client_id => TRUE ) );
if ( \IPS\Request::i()->r === 'c' )
@@ -481,7 +481,7 @@ class _oauth extends \IPS\Node\Controller
}
elseif ( \IPS\Request::i()->r === 'p' )
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=members&controller=members&do=view" )->setQueryString( 'id', \IPS\Request::i()->client_id ) );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=members&controller=members&do=view" )->setQueryString( 'id', \IPS\Request::i()->member_id ) );
}
else
{
@@ -501,13 +501,13 @@ class _oauth extends \IPS\Node\Controller
if ( \IPS\Request::i()->member_id )
{
\IPS\Db::i()->delete( 'core_oauth_server_access_tokens', array( 'member_id=?', \IPS\Request::i()->member_id ) );
\IPS\Db::i()->update( 'core_oauth_server_access_tokens', array( 'status' => 'revoked' ), array( 'member_id=?', \IPS\Request::i()->member_id ) );
\IPS\Session::i()->log( 'acplogs__oauth_revoke_member', array( \IPS\Member::load( \IPS\Request::i()->member_id )->name => FALSE ) );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=oauth&do=tokens" )->setQueryString( 'member_id', \IPS\Request::i()->member_id ) );
}
else
{
\IPS\Db::i()->delete( 'core_oauth_server_access_tokens', array( 'client_id=?', \IPS\Request::i()->client_id ) );
\IPS\Db::i()->update( 'core_oauth_server_access_tokens', array( 'status' => 'revoked' ), array( 'client_id=?', \IPS\Request::i()->client_id ) );
\IPS\Session::i()->log( 'acplogs__oauth_revoke_client', array( 'core_oauth_client_' . \IPS\Request::i()->client_id => TRUE ) );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=oauth&do=tokens" )->setQueryString( 'client_id', \IPS\Request::i()->client_id ) );
}
@@ -521,7 +521,7 @@ class _oauth extends \IPS\Node\Controller
* @param string $lastUsedTab The tab last used in the form
* @return void
*/
protected function _afterSave( \IPS\Node\Model $old = NULL, \IPS\Node\Model $new, $lastUsedTab = FALSE )
protected function _afterSave( ?\IPS\Node\Model $old, \IPS\Node\Model $new, $lastUsedTab = FALSE )
{
if ( $new->_clientSecret )
{
@@ -72,7 +72,7 @@ class _plugins extends \IPS\Node\Controller
);
}
if ( !\IPS\Request::i()->isAjax() )
if ( !\IPS\Request::i()->isAjax() AND \IPS\IPS::canManageResources() )
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('forms')->blurb( 'plugins_blurb' );
}
@@ -211,8 +211,7 @@ class _plugins extends \IPS\Node\Controller
if ( $values = $form->values() )
{
/* Already installed? */
$xml = new \IPS\Xml\XMLReader;
$xml->open( $values['plugin_upload'] );
$xml = \IPS\Xml\XMLReader::safeOpen( $values['plugin_upload'] );
if ( !@$xml->read() )
{
\IPS\Output::i()->error( 'xml_upload_invalid', '2C145/D', 403, '' );
@@ -271,8 +270,7 @@ class _plugins extends \IPS\Node\Controller
function( $data )
{
/* Open XML file */
$xml = new \IPS\Xml\XMLReader;
$xml->open( \IPS\Request::i()->file );
$xml = \IPS\Xml\XMLReader::safeOpen( \IPS\Request::i()->file );
$new = FALSE;
$xml->read();
@@ -302,6 +300,12 @@ class _plugins extends \IPS\Node\Controller
\IPS\Theme::removeTemplates( 'core', 'global', 'plugins', $plugin->id );
\IPS\Theme::removeCss( 'core', 'front', 'custom', $plugin->id );
\IPS\Theme::removeResources( 'core', 'global', 'plugins', $plugin->id );
foreach( \IPS\Db::i()->select( '*', 'core_javascript', array( 'javascript_plugin=?', $plugin->id ) ) as $javascript )
{
$jsObject = \IPS\Output\Javascript::constructFromData( $javascript );
$jsObject->delete();
}
}
catch ( \OutOfRangeException $e )
{
@@ -367,7 +371,8 @@ class _plugins extends \IPS\Node\Controller
$i = 0;
do
{
if ( ( isset( \IPS\Data\Store::i()->syncCompleted ) AND \IPS\Data\Store::i()->syncCompleted ) OR $i >= 30 ) # 30 x 0.25 seconds
if ( ( ( isset( \IPS\Data\Store::i()->syncCompleted ) AND \IPS\Data\Store::i()->syncCompleted ) OR $i >= 30 )
AND \file_exists( \IPS\SITE_FILES_PATH . "/plugins/{$plugin->location}/index.html" ) ) # 30 x 0.25 seconds
{
/* We need to wait for the backend to process the file */
sleep(3);
@@ -555,7 +560,7 @@ class _plugins extends \IPS\Node\Controller
}
catch ( \RuntimeException $e )
{
throw new \RuntimeException( \IPS\Member::loggedIn()->language()->addToStack('dev_could_not_write_data') );
throw new \RuntimeException( \IPS\Member::loggedIn()->language()->addToStack('dev_plugin_not_writable') );
}
}
@@ -647,7 +652,7 @@ class _plugins extends \IPS\Node\Controller
}
catch ( \RuntimeException $e )
{
throw new \RuntimeException( \IPS\Member::loggedIn()->language()->addToStack('dev_could_not_write_data') );
throw new \RuntimeException( \IPS\Member::loggedIn()->language()->addToStack('dev_plugin_not_writable') );
}
}
@@ -700,12 +705,6 @@ class _plugins extends \IPS\Node\Controller
case 'js':
$name = $xml->getAttribute('filename');
try
{
$js = \IPS\Output\Javascript::find( 'core', 'plugins', '/', $name );
$js->delete();
}
catch ( \OutOfRangeException $e ) {}
$js = new \IPS\Output\Javascript;
$js->plugin = $plugin->id;
@@ -883,7 +882,7 @@ class _plugins extends \IPS\Node\Controller
}
catch ( \RuntimeException $e )
{
throw new \RuntimeException( \IPS\Member::loggedIn()->language()->addToStack( 'dev_could_not_write_data' ) );
throw new \RuntimeException( \IPS\Member::loggedIn()->language()->addToStack( 'dev_plugin_not_writable' ) );
}
}
break;
@@ -985,7 +984,7 @@ class _plugins extends \IPS\Node\Controller
$widgets[ $key ] = array(
'class' => $xml->getAttribute('class'),
'restrict' => json_encode( explode( ",", $xml->getAttribute('restrict') ) ),
'restrict' => explode( ",", $xml->getAttribute('restrict') ),
'default_area' => $xml->getAttribute('default_area'),
'allow_reuse' => \intval( $xml->getAttribute('allow_reuse') ),
'menu_style' => $xml->getAttribute('menu_style'),
@@ -1005,7 +1004,7 @@ class _plugins extends \IPS\Node\Controller
}
catch ( \RuntimeException $e )
{
throw new \RuntimeException( \IPS\Member::loggedIn()->language()->addToStack( 'dev_could_not_write_data' ) );
throw new \RuntimeException( \IPS\Member::loggedIn()->language()->addToStack( 'dev_plugin_not_writable' ) );
}
}
@@ -1310,13 +1309,16 @@ class _plugins extends \IPS\Node\Controller
if ( $activeTabContents )
{
/* Add Download Button */
\IPS\Output::i()->sidebar['actions'] = array(
'download' => array(
'icon' => 'download',
'title' => 'download',
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=plugins&do=download&id={$plugin->id}" )->csrf(),
)
);
if( !$plugin->marketplace_id )
{
\IPS\Output::i()->sidebar['actions'] = array(
'download' => array(
'icon' => 'download',
'title' => 'download',
'link' => \IPS\Http\Url::internal("app=core&module=applications&controller=plugins&do=download&id={$plugin->id}")->csrf(),
)
);
}
\IPS\Output::i()->title = $plugin->name;
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->tabs( $tabs, $activeTab, $activeTabContents, \IPS\Http\Url::internal( "app=core&module=applications&controller=plugins&do=developer&id={$plugin->id}" ) );
@@ -1691,7 +1693,13 @@ class _plugins extends \IPS\Node\Controller
$defaults['long'] = $long + 1;
break;
}
$form->add( new \IPS\Helpers\Form\Text( 'versions_human', $defaults['human'], TRUE ) );
$form->add( new \IPS\Helpers\Form\Text( 'versions_human', $defaults['human'], TRUE, [], function( $val )
{
if ( !preg_match( '/^([0-9]+\.[0-9]+\.[0-9]+)/', $val ) )
{
throw new \DomainException( 'versions_human_bad' );
}
} ) );
$form->add( new \IPS\Helpers\Form\Text( 'versions_long', $defaults['long'], TRUE, array(), function( $val ) use ( $json )
{
if ( !preg_match( '/^\d*$/', $val ) )
@@ -1790,6 +1798,11 @@ class _plugins extends \IPS\Node\Controller
*/
public function download()
{
if( !\IPS\IN_DEV )
{
\IPS\Output::i()->error( 'not_in_dev', '2C145/R', 403, '' );
}
\IPS\Session::i()->csrfCheck();
/* Load */
@@ -1802,6 +1815,12 @@ class _plugins extends \IPS\Node\Controller
\IPS\Output::i()->error( 'node_error', '2C145/B', 404, '' );
}
/* Don't allow downloads of Marketplace resources */
if( $plugin->marketplace_id )
{
\IPS\Output::i()->error( 'plugin_cannot_build_marketplace', '2C145/Q', 403, '' );
}
/* Init */
$xml = \IPS\Xml\SimpleXML::create('plugin');
$xml->addAttribute( 'name', $plugin->name );
@@ -2129,7 +2148,7 @@ class _plugins extends \IPS\Node\Controller
}
/* Build */
\IPS\Output::i()->sendOutput( $xml->asXML(), 200, 'application/xml', array( "Content-Disposition" => \IPS\Output::getContentDisposition( 'attachment', $plugin->name . '.xml' ) ), FALSE, FALSE, FALSE );
\IPS\Output::i()->sendOutput( $xml->asXML(), 200, 'application/xml', array( "Content-Disposition" => \IPS\Output::getContentDisposition( 'attachment', "{$plugin->name} {$plugin->version_human}.xml" ) ), FALSE, FALSE, FALSE );
}
/**
@@ -265,6 +265,11 @@ class _rss extends \IPS\Node\Controller
}
} ) );
$form->add( new \IPS\Helpers\Form\Text( 'rss_import_showlink', \IPS\Member::loggedIn()->language()->addToStack('rss_import_showlink_default') ) );
$form->add( new \IPS\Helpers\Form\Radio( 'rss_import_enclosures', 'import', FALSE, array( 'options' => array(
'import' => "rss_import_enclosures_import",
'hotlink' => "rss_import_enclosures_hotlink",
'ignore' => "rss_import_enclosures_ignore",
) ) ) );
$form->add( new \IPS\Helpers\Form\Text( 'rss_import_topic_pre', NULL, FALSE, array( 'trim' => FALSE ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'rss_import_auto_follow', NULL, FALSE, array(), NULL, NULL, NULL, 'rss_import_auto_follow' ) );
$extension->form( $form, $import );
@@ -281,6 +286,13 @@ class _rss extends \IPS\Node\Controller
$import->auth_user = ( $data['rss_import_auth_user'] or $data['rss_import_auth_pass'] ) ? $data['rss_import_auth_user'] : NULL;
$import->auth_pass = ( $data['rss_import_auth_user'] or $data['rss_import_auth_pass'] ) ? $data['rss_import_auth_pass'] : NULL;
$import->auto_follow = $values['rss_import_auto_follow'];
$import->enclosures = $values['rss_import_enclosures'];
/* If we are ignoring enclosures, reset the has_enclosures flag */
if( $import->enclosures == 'ignore' )
{
$import->has_enclosures = false;
}
if ( $settings = $extension->saveForm( $values, $import ) )
{
@@ -304,6 +304,8 @@ class _clubs extends \IPS\Dispatcher\Controller
/* Approve */
$club->approved = TRUE;
$club->save();
$club->owner->achievementAction( 'core', 'NewClub', $club );
/* Redirect */
$target = \IPS\Http\Url::internal("app=core&module=clubs&controller=clubs");
@@ -232,7 +232,15 @@ class _ajax extends \IPS\Dispatcher\Controller
{
if ( $result['template_user_edited'] )
{
$outOfDate = $result['template_user_edited'] < \IPS\Application::load( $result['template_app'] )->long_version;
try
{
$outOfDate = $result['template_user_edited'] < \IPS\Application::load( $result['template_app'] )->long_version;
}
catch( \UnexpectedValueException | \OutOfRangeException $e )
{
unset( $return[ $result['template_app'] ][ $result['template_location'] ][ $result['template_group'] ][ $result['template_name'] ] );
continue;
}
if ( ( $outOfDate and !\in_array( 'outofdate', explode( ',', \IPS\Request::i()->filters ) ) ) or ( !$outOfDate and !\in_array( 'modified', explode( ',', \IPS\Request::i()->filters ) ) ) )
{
@@ -313,8 +321,16 @@ class _ajax extends \IPS\Dispatcher\Controller
{
if ( $result['css_user_edited'] )
{
$outOfDate = $result['css_user_edited'] < \IPS\Application::load( $result['css_app'] )->long_version;
try
{
$outOfDate = $result['css_user_edited'] < \IPS\Application::load( $result['css_app'] )->long_version;
}
catch( \UnexpectedValueException | \OutOfRangeException $e )
{
unset( $return[ $result['css_app'] ][ $result['css_location'] ][ $result['css_path'] ][ $result['css_name'] ] );
continue;
}
if ( !$result['css_added_to'] and ( ( $outOfDate and !\in_array( 'outofdate', explode( ',', \IPS\Request::i()->filters ) ) ) or ( !$outOfDate and !\in_array( 'modified', explode( ',', \IPS\Request::i()->filters ) ) ) ) )
{
unset( $return[ $result['css_app'] ][ $result['css_location'] ][ $result['css_path'] ][ $result['css_name'] ] );
@@ -370,7 +370,7 @@ class _emails extends \IPS\Dispatcher\Controller
*/
public function emailPreview()
{
$email = \IPS\Email::buildFromContent( '', \IPS\Member::loggedIn()->language()->addToStack( 'email_preview_content' ) );
$email = \IPS\Email::buildFromContent( '', \IPS\Member::loggedIn()->language()->addToStack( 'email_preview_content' ), NULL, \IPS\Email::TYPE_TRANSACTIONAL );
\IPS\Output::i()->sendOutput( $email->compileContent( 'html' ) );
}
}
@@ -58,6 +58,10 @@ 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' ) ) );
/* Apple startup screen logo - we accept one upload and create the images we need */
$apple = json_decode( \IPS\Settings::i()->icons_apple_startup, TRUE ) ?? array();
$form->add( new \IPS\Helpers\Form\Upload( 'icons_apple_startup', ( isset( $apple['original'] ) ) ? \IPS\File::get( 'core_Icons', $apple['original'] ) : NULL, FALSE, array( 'image' => true, 'storageExtension' => 'core_Icons' ) ) );
/* Safari pinned tabs icon and highlight color */
$form->add( new \IPS\Helpers\Form\Upload( 'icons_mask_icon', \IPS\Settings::i()->icons_mask_icon ? \IPS\File::get( 'core_Icons', \IPS\Settings::i()->icons_mask_icon ) : NULL, FALSE, array( 'allowedFileTypes' => array( 'svg' ), 'storageExtension' => 'core_Icons', 'obscure' => FALSE ) ) );
$form->add( new \IPS\Helpers\Form\Color( 'icons_mask_color', \IPS\Settings::i()->icons_mask_color, FALSE ) );
@@ -129,9 +133,34 @@ class _icons extends \IPS\Dispatcher\Controller
}
}
/* Homescreen icons are the hardest part, as we need to generate different sizes.. */
/* Homescreen icon is the hardest part, as we need to generate different sizes.. */
$values = static::processApplicationIcon( $values, $homeScreen );
/* The start screen requires more different sizes so we'll rebuild that after submit */
$rebuildStartScreen = FALSE;
if( $values['icons_apple_startup'] AND ( !isset( $apple['original'] ) OR !$apple['original'] OR (string) $values['icons_apple_startup'] != $apple['original'] ) )
{
$rebuildStartScreen = TRUE;
}
if( ( !isset( $manifestDetails['background_color'] ) AND $values['manifest_bgcolor'] ) OR ( isset( $manifestDetails['background_color'] ) AND !$values['manifest_bgcolor'] ) OR ( isset( $manifestDetails['background_color'] ) AND $values['manifest_bgcolor'] != $manifestDetails['background_color'] ) )
{
if( $values['icons_apple_startup'] )
{
$rebuildStartScreen = TRUE;
}
}
if( $rebuildStartScreen )
{
$values = static::processAppleStartupScreen( $values, $apple );
}
else
{
$values['icons_apple_startup'] = json_encode( $apple );
}
/* We need the string value of this uploaded file as well */
$values['icons_mask_icon'] = (string) $values['icons_mask_icon'];
@@ -166,7 +195,14 @@ class _icons extends \IPS\Dispatcher\Controller
\IPS\Session::i()->log( 'acplogs__icons_and_logos' );
/* And Redirect */
\IPS\Output::i()->redirect( $this->url , 'saved' );
if( $rebuildStartScreen === TRUE )
{
\IPS\Output::i()->redirect( $this->url->setQueryString( 'do', 'buildStartupScreenImages' ), 'saved' );
}
else
{
\IPS\Output::i()->redirect( $this->url, 'saved' );
}
}
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_customization_icons');
@@ -253,4 +289,182 @@ class _icons extends \IPS\Dispatcher\Controller
return $values;
}
/**
* @brief Minimum padding on either side of startup image (in px)
*/
const MINIMUM_STARTUP_IMAGE_PADDING = 50;
/**
* Process Apple startup screen images
*
* @param array $values Values from form submission
* @param array $apple Existing values, if any
* @return array
*/
public static function processAppleStartupScreen( $values, $apple = array() )
{
if( $values['icons_apple_startup'] )
{
$values['icons_apple_startup'] = json_encode( array( 'original' => (string) $values['icons_apple_startup'] ) );
}
else
{
/* Delete any images that may already exist */
foreach( $apple as $key => $image )
{
try
{
\IPS\File::get( 'core_Icons', ( $key == 'original' ) ? $image : $image['url'] )->delete();
}
catch( \Exception $e ){}
}
$values['icons_apple_startup'] = '';
}
return $values;
}
/**
* Process Apple startup screen images
*
* @param array $values Values from form submission
* @param array $apple Existing values, if any
* @return array
*/
public function buildStartupScreenImages()
{
$self = $this;
$multiRedirect = new \IPS\Helpers\MultipleRedirect(
$this->url->setQueryString('do', 'buildStartupScreenImages'),
function( $data )
{
/* Get the necessary data */
$manifestDetails = json_decode( \IPS\Settings::i()->manifest_details, TRUE );
$setting = json_decode( \IPS\Settings::i()->icons_apple_startup, TRUE ) ?? array();
if( isset( $setting['original'] ) )
{
$sizes = array(
'apple-startup-1136x640' => array( 320, 568, 2, 'landscape' ),
'apple-startup-2436x1125' => array( 375, 812, 3, 'landscape' ),
'apple-startup-1792x828' => array( 414, 896, 2, 'landscape' ),
'apple-startup-828x1792' => array( 414, 896, 2, 'portrait' ),
'apple-startup-1334x750' => array( 375, 667, 2, 'landscape' ),
'apple-startup-1242x2688' => array( 414, 896, 3, 'portrait' ),
'apple-startup-2208x1242' => array( 414, 736, 3, 'landscape' ),
'apple-startup-1125x2436' => array( 375, 812, 3, 'portrait' ),
'apple-startup-1242x2208' => array( 414, 736, 3, 'portrait' ),
'apple-startup-2732x2048' => array( 1024, 1366, 2, 'landscape' ),
'apple-startup-2688x1242' => array( 414, 896, 3, 'landscape' ),
'apple-startup-2224x1668' => array( 834, 1112, 2, 'landscape' ),
'apple-startup-750x1334' => array( 375, 667, 2, 'portrait' ),
'apple-startup-2048x2732' => array( 1024, 1366, 2, 'portrait' ),
'apple-startup-2388x1668' => array( 834, 1194, 2, 'landscape' ),
'apple-startup-1668x2224' => array( 834, 1112, 2, 'portrait' ),
'apple-startup-640x1136' => array( 320, 568, 2, 'portrait' ),
'apple-startup-1668x2388' => array( 834, 1194, 2, 'portrait' ),
'apple-startup-2048x1536' => array( 768, 1024, 2, 'landscape' ),
'apple-startup-1536x2048' => array( 768, 1024, 2, 'portrait' ),
'apple-startup-2360x1640' => array( 1180, 820, 2, 'landscape' ),
'apple-startup-1640x2360' => array( 1180, 820, 2, 'portrait' ),
'apple-startup-2160x1620' => array( 1080, 810, 2, 'landscape' ),
'apple-startup-1620x2160' => array( 1080, 810, 2, 'portrait' ),
'apple-startup-2778x1284' => array( 428, 926, 3, 'landscape' ),
'apple-startup-1284x2778' => array( 428, 926, 3, 'portrait' ),
'apple-startup-2532x1170' => array( 390, 844, 3, 'landscape' ),
'apple-startup-1170x2532' => array( 390, 844, 3, 'portrait' ),
'apple-startup-2340x1080' => array( 360, 780, 3, 'landscape' ),
'apple-startup-1080x2340' => array( 360, 780, 3, 'portrait' ),
);
$file = \IPS\File::get( 'core_Icons', $setting['original'] );
$originalContents = $file->contents();
$backgroundColor = ( isset( $manifestDetails['background_color'] ) ) ? str_replace( '#', '', $manifestDetails['background_color'] ) : 'FFFFFF';
$rgb = array();
if ( \strlen( $backgroundColor ) == 3 )
{
$rgb[] = hexdec( \substr( $backgroundColor, 0, 1 ) . \substr( $backgroundColor, 0, 1 ) ); // R
$rgb[] = hexdec( \substr( $backgroundColor, 1, 1 ) . \substr( $backgroundColor, 1, 1 ) ); // G
$rgb[] = hexdec( \substr( $backgroundColor, 2, 1 ) . \substr( $backgroundColor, 2, 1 ) ); // B
}
else
{
$rgb[] = hexdec( \substr( $backgroundColor, 0, 2 ) ); // R
$rgb[] = hexdec( \substr( $backgroundColor, 2, 2 ) ); // G
$rgb[] = hexdec( \substr( $backgroundColor, 4, 2 ) ); // B
}
$did = 0;
foreach( $sizes as $filename => $_sizes )
{
$did++;
if( isset( $setting[ $filename ] ) )
{
continue;
}
try
{
$width = ( $_sizes[3] == 'landscape' ) ? $_sizes[1] * $_sizes[2] : $_sizes[0] * $_sizes[2];
$height = ( $_sizes[3] == 'portrait' ) ? $_sizes[1] * $_sizes[2] : $_sizes[0] * $_sizes[2];
$logoImage = \IPS\Image::create( $originalContents );
$canvas = \IPS\Image::newImageCanvas( $width, $height, $rgb );
$logoImage->resizeToMax( $width - static::MINIMUM_STARTUP_IMAGE_PADDING, $height - static::MINIMUM_STARTUP_IMAGE_PADDING );
$xPos = ( $width - $logoImage->width ) / 2;
$yPos = ( $height - $logoImage->height ) / 2;
$canvas->impose( $logoImage, $xPos, $yPos );
if( $canvas::exifSupported() )
{
$canvas->setExifData( $originalContents );
}
$setting[ $filename ] = array(
'url' => (string) \IPS\File::create( 'core_Icons', $filename . '.png', (string) $canvas, NULL, TRUE, NULL, FALSE ),
'width' => $canvas->width,
'height' => $canvas->height,
'density' => $_sizes[2],
'orientation' => $_sizes[3],
);
}
catch ( \Exception $e )
{
\IPS\Log::log( $e, 'apple-startup-image' );
}
break;
}
/* Are we done? */
if( $did == \count( $sizes ) )
{
return NULL;
}
\IPS\Settings::i()->changeValues( array( 'icons_apple_startup' => json_encode( $setting ) ) );
}
return array( $data, \IPS\Member::loggedIn()->language()->addToStack('build_start_images_title'), ( $did ) ? round( ( 100 / \count( $sizes ) * $did ), 2 ) : 0 );
},
function() use( $self )
{
\IPS\Output::i()->redirect( $self->url, 'completed' );
}
);
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('build_start_images_title');
\IPS\Output::i()->output = $multiRedirect;
}
}
@@ -546,7 +546,7 @@ class _themes extends \IPS\Node\Controller
}
/* Start importing */
$data = array( 'buildingDesignersFiles' => true, 'toBuild' => $toBuild, 'extensions' => $extensions, 'totalItems' => \count( array_keys( $toBuild ) ) + \count( array_keys( $extensions ) ), 'totalDone' => 0, 'extra' => array() );
$data = array( 'buildingDesignersFiles' => true, 'toBuild' => $toBuild, 'extensions' => $extensions, 'totalItems' => \count( array_keys( $toBuild ) ) * 3 + \count( array_keys( $extensions ) ), 'totalDone' => 0, 'extra' => array() );
return array( $data, \IPS\Member::loggedIn()->language()->addToStack('theme_dm_building') );
}
@@ -664,7 +664,7 @@ class _themes extends \IPS\Node\Controller
$table->parsers = array(
'resource_data' => function( $val, $row ) use ($set)
{
if ( \in_array( mb_substr( $row['resource_name'], mb_strrpos( $row['resource_name'], '.' ) + 1 ), array_merge( \IPS\Image::$imageExtensions, array( 'svg' ) ) ) )
if ( \in_array( mb_substr( $row['resource_name'], mb_strrpos( $row['resource_name'], '.' ) + 1 ), array_merge( \IPS\Image::supportedExtensions(), array( 'svg' ) ) ) )
{
$row['resource_filename'] = $set->resource( ltrim( $row['resource_path'], '/' ) . $row['resource_name'], $row['resource_app'], $row['resource_location'] );
}
@@ -674,7 +674,7 @@ class _themes extends \IPS\Node\Controller
'resource_name' => function( $val, $row ) use ($set)
{
if ( \in_array( mb_substr( $row['resource_name'], mb_strrpos( $row['resource_name'], '.' ) + 1 ), array_merge( \IPS\Image::$imageExtensions, array( 'svg' ) ) ) )
if ( \in_array( mb_substr( $row['resource_name'], mb_strrpos( $row['resource_name'], '.' ) + 1 ), array_merge( \IPS\Image::supportedExtensions(), array( 'svg' ) ) ) )
{
$row['resource_filename'] = $set->resource( ltrim( $row['resource_path'], '/' ) . $row['resource_name'], $row['resource_app'], $row['resource_location'] );
}
@@ -864,7 +864,7 @@ class _themes extends \IPS\Node\Controller
\IPS\Member::loggedIn()->language()->words['core_theme_resource_name_choice_o_filename_desc'] = sprintf( \IPS\Member::loggedIn()->language()->get('core_theme_resource_name_choice_o_filename_desc'), $current['resource_name'] );
}
$form->add( new \IPS\Helpers\Form\Upload( 'core_theme_resource_filename', ( isset( $current['resource_filename'] ) ? \IPS\File::get( 'core_Theme', $current['resource_filename'] ) : NULL ), TRUE, array( 'obscure' => FALSE, 'maxFileSize' => 1.2, 'storageExtension' => 'core_Theme', 'storageContainer' => 'set_resources_' . $set->_id ), NULL, NULL, NULL, 'core_theme_resource_filename' ) );
$form->add( new \IPS\Helpers\Form\Upload( 'core_theme_resource_filename', ( isset( $current['resource_filename'] ) ? \IPS\File::get( 'core_Theme', $current['resource_filename'] ) : NULL ), TRUE, array( 'obscure' => ( $id ) ? TRUE : FALSE, 'maxFileSize' => 1.2, 'storageExtension' => 'core_Theme', 'storageContainer' => 'set_resources_' . $set->_id ), NULL, NULL, NULL, 'core_theme_resource_filename' ) );
if ( $values = $form->values() )
{
@@ -919,12 +919,14 @@ class _themes extends \IPS\Node\Controller
/* Resource may be used in CSS */
\IPS\File::getClass('core_Theme')->deleteContainer( 'css_built_' . $set->id );
$set->css_map = array();
$set->css_updated = time();
$set->save();
foreach( $set->children() as $child )
{
\IPS\File::getClass('core_Theme')->deleteContainer( 'css_built_' . $child->id );
$child->css_map = array();
$child->css_updated = time();
$child->save();
}
@@ -1101,8 +1103,7 @@ class _themes extends \IPS\Node\Controller
$masterCss = \IPS\Theme::load( \IPS\Request::i()->id )->getRawCss( null, null, null, \IPS\Theme::RETURN_BIT_NAMES, false );
/* Open XML file */
$xml = new \IPS\Xml\XMLReader;
$xml->open( \IPS\Request::i()->file );
$xml = \IPS\Xml\XMLReader::safeOpen( \IPS\Request::i()->file );
if ( ! @$xml->read() )
{
@@ -1489,11 +1490,7 @@ class _themes extends \IPS\Node\Controller
$url = (string) \IPS\File::create( 'core_Theme', $name, base64_decode( $xml->readString() ) );
$image = \IPS\Image::create( base64_decode( $xml->readString() ) );
$width = $image->width;
$height = $image->height;
$set->saveSet( array( 'logo' => array( $type => array( 'url' => $url, 'width' => $width, 'height' => $height ) ) ) );
$set->saveSet( array( 'logo' => array( $type => array( 'url' => $url, 'height' => $xml->getAttribute('height') ?? 100 ) ) ) );
}
$xml->next();
@@ -1528,6 +1525,9 @@ class _themes extends \IPS\Node\Controller
\IPS\Theme::setThemeCustomized( $set->id );
$set->resetCacheKey();
/* Update children */
$set->updateChildrenThemeSettings();
/* Conflicts to fix? */
if ( \IPS\Db::i()->select( 'count(*)', 'core_theme_conflict', array( 'conflict_set_id=?', \IPS\Request::i()->id ) )->first() )
@@ -2014,6 +2014,10 @@ class _themes extends \IPS\Node\Controller
$xml->text( $file );
$xml->endAttribute();
$xml->startAttribute('height');
$xml->text( $data['height'] );
$xml->endAttribute();
/* Get image data */
$original = \IPS\File::get( 'core_Theme', $data['url'] );
@@ -2311,10 +2315,8 @@ class _themes extends \IPS\Node\Controller
/* Finish */
$xml->endDocument();
$name = addslashes( str_replace( array( ' ', '.', ',' ), '_', \IPS\Member::loggedIn()->language()->get('core_theme_set_title_' . $themeSet->_id ) ) . '.xml' );
\IPS\Output::i()->sendOutput( $xml->outputMemory(), 200, 'application/xml', array( 'Content-Disposition' => \IPS\Output::getContentDisposition( 'attachment', $name ), FALSE, FALSE ) );
\IPS\Output::i()->sendOutput( $xml->outputMemory(), 200, 'application/xml', array( 'Content-Disposition' => \IPS\Output::getContentDisposition( 'attachment', \IPS\Member::loggedIn()->language()->get('core_theme_set_title_' . $themeSet->_id ) . " {$version}.xml" ), FALSE, FALSE ) );
}
/* Display */
@@ -3035,7 +3037,7 @@ class _themes extends \IPS\Node\Controller
{
$form = new \IPS\Helpers\Form;
$form->hiddenvalues['id'] = \IPS\Request::i()->id;
$form->add( new \IPS\Helpers\Form\Select( 'member_reset_where', '*', TRUE, array( 'options' => \IPS\Member\Group::groups( TRUE, FALSE ), 'multiple' => TRUE, 'parse' => 'normal', 'unlimited' => '*', 'unlimitedLang' => 'all' ) ) );
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'member_reset_where', '*', TRUE, array( 'options' => \IPS\Member\Group::groups( TRUE, FALSE ), 'multiple' => TRUE, 'parse' => 'normal', 'unlimited' => '*', 'unlimitedLang' => 'all', 'impliedUnlimited' => TRUE ) ) );
if ( $values = $form->values() )
{
@@ -286,7 +286,7 @@ class _themesettings extends \IPS\Dispatcher\Controller
$form = new \IPS\Helpers\Form();
$form->hiddenValues['set_id'] = $setId;
$form->add( new \IPS\Helpers\Form\Text( 'theme_custom_setting_title', $current['sc_title'], TRUE ) );
$form->add( new \IPS\Helpers\Form\Text( 'theme_custom_setting_title', $id ? $current['sc_title'] : null, TRUE ) );
$form->add( new \IPS\Helpers\Form\Select( 'theme_custom_setting_app', $id ? $current['sc_app'] : null, TRUE, array( 'options' => $apps ) ) );
@@ -469,7 +469,7 @@ class _themesettings extends \IPS\Dispatcher\Controller
}
/* Default is changed, change value? */
if ( $save['sc_default'] !== $current['sc_default'] )
if ( $current and ( $save['sc_default'] !== $current['sc_default'] ) )
{
try
{
@@ -501,6 +501,9 @@ class _themesettings extends \IPS\Dispatcher\Controller
$set->template_settings = json_encode( $themeSettings );
$set->save();
}
/* Update children */
$set->updateChildrenThemeSettings();
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=themesettings&set_id=' . $setId ), 'saved' );
}
@@ -253,8 +253,8 @@ class _toolbar extends \IPS\Dispatcher\Controller
$form->addTab('permissions');
}
$form->add( new \IPS\Helpers\Form\Select( 'editor_permission_groups', isset( $current[ $button ] ) ? $current[ $button ]['groups'] : '*', TRUE, array( 'options' => \IPS\Member\Group::groups(), 'parse' => 'normal', 'multiple' => TRUE, 'unlimited' => '*', 'unlimitedLang' => 'everyone' ), NULL, NULL, NULL, $b64Button ) );
$form->add( new \IPS\Helpers\Form\Select( 'editor_permission_areas', isset( $current[ $button ] ) ? $current[ $button ]['areas'] : '*', TRUE, array( 'options' => $areas, 'multiple' => TRUE, 'unlimited' => '*', 'unlimitedLang' => 'everywhere' ), NULL, NULL, NULL, 'l' . $b64Button ) );
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'editor_permission_groups', isset( $current[ $button ] ) ? $current[ $button ]['groups'] : '*', TRUE, array( 'options' => \IPS\Member\Group::groups(), 'parse' => 'normal', 'multiple' => TRUE, 'unlimited' => '*', 'unlimitedLang' => 'everyone', 'impliedUnlimited' => TRUE ), NULL, NULL, NULL, $b64Button ) );
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'editor_permission_areas', isset( $current[ $button ] ) ? $current[ $button ]['areas'] : '*', TRUE, array( 'options' => $areas, 'multiple' => TRUE, 'unlimited' => '*', 'unlimitedLang' => 'everywhere', 'impliedUnlimited' => TRUE ), NULL, NULL, NULL, 'l' . $b64Button ) );
/* If this is a custom one, add a delete button */
if ( \in_array( mb_strtolower( $button ), explode( ',', mb_strtolower( \IPS\Settings::i()->ckeditor_extraPlugins ) ) ) or preg_match( '/^custom-\w{32}$/i', $button ) )
@@ -421,6 +421,8 @@ class _toolbar extends \IPS\Dispatcher\Controller
}
}
while( TRUE );
$pluginDirectory = \IPS\SITE_FILES_PATH . '/ckeditor_custom/' . $name;
}
else
{
@@ -435,10 +437,6 @@ class _toolbar extends \IPS\Dispatcher\Controller
}
$pluginDirectory = \IPS\ROOT_PATH . '/applications/core/interface/ckeditor/ckeditor/plugins/' . $name;
if ( \IPS\CIC2 )
{
$pluginDirectory = \IPS\SITE_FILES_PATH . '/ckeditor_custom/' . $name;
}
/* Check it isn't already installed */
if ( \in_array( $name, array_merge( static::$defaultPlugins, explode( ',', \IPS\Settings::i()->ckeditor_extraPlugins ) ) ) )
@@ -482,41 +480,50 @@ class _toolbar extends \IPS\Dispatcher\Controller
}
if ( !file_exists( $path ) )
{
/* If we just extracted some files remove them as the plugin was apparently not valid */
if ( isset( $values['editor_plugin_zip'] ) )
/* If we're on Cloud2, sleep for a few seconds to make sure the plugin isn't just still being transferred */
if( \IPS\CIC2 )
{
if( \is_dir( $pluginDirectory ) )
{
if ( \IPS\CIC2 )
{
\IPS\Cicloud\deleteFolder( 'ckeditor_custom/' . $name );
}
else
{
try
{
$iterator = new \RecursiveDirectoryIterator( $pluginDirectory, \FilesystemIterator::SKIP_DOTS );
foreach ( new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::CHILD_FIRST ) as $file )
{
if ( $file->isDir() )
{
@rmdir( $file->getPathname() );
}
else
{
@unlink( $file->getPathname() );
}
}
$handle = opendir( $pluginDirectory );
closedir ( $pluginDirectory );
@rmdir( $pluginDirectory );
}
catch( \UnexpectedValueException $e ){}
}
}
sleep(5);
}
\IPS\Output::i()->error( 'editor_bad_plugin', '1C120/B', 400, '' );
if( !file_exists( $path ) )
{
/* If we just extracted some files remove them as the plugin was apparently not valid */
if ( isset( $values['editor_plugin_zip'] ) )
{
if( \is_dir( $pluginDirectory ) )
{
if ( \IPS\CIC2 )
{
\IPS\Cicloud\folderDelete( 'ckeditor_custom/' . $name );
}
else
{
try
{
$iterator = new \RecursiveDirectoryIterator( $pluginDirectory, \FilesystemIterator::SKIP_DOTS );
foreach ( new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::CHILD_FIRST ) as $file )
{
if ( $file->isDir() )
{
@rmdir( $file->getPathname() );
}
else
{
@unlink( $file->getPathname() );
}
}
$handle = opendir( $pluginDirectory );
closedir ( $pluginDirectory );
@rmdir( $pluginDirectory );
}
catch( \UnexpectedValueException $e ){}
}
}
}
\IPS\Output::i()->error( 'editor_bad_plugin', '1C120/B', 400, '' );
}
}
$file = \file_get_contents( $path );
@@ -545,7 +552,16 @@ class _toolbar extends \IPS\Dispatcher\Controller
{
return str_replace( 'contextmenu', 'ipscontextmenu', $matches[0] );
}, $file );
\file_put_contents( \IPS\ROOT_PATH . '/applications/core/interface/ckeditor/ckeditor/plugins/' . $name . '/plugin.js', $file );
if( \IPS\CIC2 )
{
sleep(5); // @Mark
\IPS\Cicloud\file( 'plugin.js', $file, 'ckeditor_custom/' . $name );
}
else
{
\file_put_contents( \IPS\ROOT_PATH . '/applications/core/interface/ckeditor/ckeditor/plugins/' . $name . '/plugin.js', $file );
}
}
}
@@ -952,7 +968,7 @@ JS;
{
if ( \IPS\CIC2 )
{
\IPS\Cicloud\deleteFolder( "ckeditor_custom/{$key}" );
\IPS\Cicloud\folderDelete( "ckeditor_custom/{$key}" );
}
else
{
@@ -121,7 +121,7 @@ class _languages extends \IPS\Node\Controller
*
* @return void
*/
protected function addWord(): void
protected function addWord()
{
$form = new \IPS\Helpers\Form( 'wordForm', 'save', NULL, array( 'data-role' => 'wordForm' ) );
\IPS\Lang::wordForm( $form );
@@ -150,7 +150,7 @@ class _languages extends \IPS\Node\Controller
*
* @return void
*/
protected function deleteWord(): void
protected function deleteWord()
{
\IPS\Session::i()->csrfCheck();
@@ -336,7 +336,7 @@ class _languages extends \IPS\Node\Controller
if ( !\IPS\Request::i()->status )
{
$count = \IPS\Db::i()->select( 'count(*)', 'core_members', array( 'language=?', $language->_id ) )->first();
if ( $count['count'] )
if ( $count )
{
if ( \IPS\Request::i()->isAjax() )
{
@@ -851,7 +851,7 @@ class _languages extends \IPS\Node\Controller
/* Finish */
$xml->endDocument();
\IPS\Output::i()->sendOutput( $xml->outputMemory(), 200, 'application/xml', array( 'Content-Disposition' => \IPS\Output::getContentDisposition( 'attachment', $lang->title . '.xml' ) ), FALSE, FALSE, FALSE );
\IPS\Output::i()->sendOutput( $xml->outputMemory(), 200, 'application/xml', array( 'Content-Disposition' => \IPS\Output::getContentDisposition( 'attachment', $lang->title . " {$lang->version}.xml" ) ), FALSE, FALSE, FALSE );
}
/* Display */
@@ -920,8 +920,7 @@ class _languages extends \IPS\Node\Controller
function( $data )
{
/* Open XML file */
$xml = new \IPS\Xml\XMLReader;
$xml->open( \IPS\Request::i()->file );
$xml = \IPS\Xml\XMLReader::safeOpen( \IPS\Request::i()->file );
$xml->read();
/* If this is the first batch, create the language record */
@@ -1160,7 +1159,7 @@ class _languages extends \IPS\Node\Controller
{
$form = new \IPS\Helpers\Form;
$form->hiddenvalues['id'] = \IPS\Request::i()->id;
$form->add( new \IPS\Helpers\Form\Select( 'member_reset_where', '*', TRUE, array( 'options' => \IPS\Member\Group::groups( TRUE, FALSE ), 'multiple' => TRUE, 'parse' => 'normal', 'unlimited' => '*', 'unlimitedLang' => 'all' ) ) );
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'member_reset_where', '*', TRUE, array( 'options' => \IPS\Member\Group::groups( TRUE, FALSE ), 'multiple' => TRUE, 'parse' => 'normal', 'unlimited' => '*', 'unlimitedLang' => 'all', 'impliedUnlimited' => TRUE ) ) );
if ( $values = $form->values() )
{
@@ -32,7 +32,7 @@ class _marketplace extends \IPS\Dispatcher\Controller
*
* @return void
*/
public function execute(): void
public function execute()
{
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'admin_marketplace.js', 'core', 'admin' ) );
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'marketplace/marketplace.css', 'core', 'admin' ) );
@@ -59,7 +59,7 @@ class _marketplace extends \IPS\Dispatcher\Controller
$tree = $this->_buildTree( $response['results'] );
}
catch( \Throwable | \IPS\Http\Exception $e )
catch( \Throwable $e )
{
return [];
}
@@ -75,7 +75,7 @@ class _marketplace extends \IPS\Dispatcher\Controller
*
* @return void
*/
final protected function manage(): void
final protected function manage()
{
try
{
@@ -101,17 +101,45 @@ class _marketplace extends \IPS\Dispatcher\Controller
}
/**
* Install resource
* Renew Resource
*
* @return void
*/
final protected function install(): void
final protected function renewFile()
{
\IPS\Session::i()->csrfCheck();
/* Check License Validity */
$this->_licenseCheck();
if ( $this->_token() )
{
$response = $this->_apiWrapper( "marketplace/files/" . (int) \IPS\Request::i()->id . "/renew/" . (int) \IPS\Request::i()->invoiceId, NULL, TRUE, [] );
if ( isset( $response['errorCode'] ) )
{
\IPS\Output::i()->error( 'marketplace_cannot_purchase', '2C409/J', 403, '', array(), $response['errorMessage'] );
}
else
{
\IPS\Output::i()->redirect( \IPS\Http\Url::external( $response['checkoutUrl'] ) );
}
}
else
{
\IPS\Output::i()->error( 'marketplace_sign_in_required', '2C409/K', 403, '' );
}
}
/**
* Install resource
*
* @return void
*/
final protected function install()
{
\IPS\Session::i()->csrfCheck();
if ( \IPS\DEMO_MODE )
{
\IPS\Output::i()->error( 'demo_mode_function_blocked', '2C409/4', 403, '' );
@@ -155,6 +183,18 @@ 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'] ) )
{
\IPS\Output::i()->error( 'marketplace_resource_not_purchased', '2C171/A', 403, '' );
}
/* If we can still download this paid file, don't check license */
elseif( !( $file['isPaid'] AND $file['isPurchased'] AND $file['canDownload'] ) )
{
/* Check License Validity */
$this->_licenseCheck();
}
if( empty( \IPS\Request::i()->confirm ) AND \in_array( $file['disclaimerLocation'], [ 'both', 'download'] ) AND !empty( $file['downloadDisclaimer'] ) )
{
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('marketplace_terms_and_conditions');
@@ -258,6 +298,12 @@ class _marketplace extends \IPS\Dispatcher\Controller
}
catch( \Throwable $e )
{
if( $fileExtension == 'tar' )
{
/* Application install renames the temporary file */
$temporaryFile .= '.tar';
}
@unlink( $temporaryFile );
\IPS\Log::log( $e, 'mp_install_error' );
\IPS\Output::i()->error( 'marketplace_error', '2C409/D', 403, '' );
@@ -269,7 +315,7 @@ class _marketplace extends \IPS\Dispatcher\Controller
*
* @return void
*/
final protected function viewCategory(): void
final protected function viewCategory()
{
$page = \IPS\Request::i()->page ?? 1;
try
@@ -309,7 +355,7 @@ class _marketplace extends \IPS\Dispatcher\Controller
*
* @return void
*/
final protected function viewFile(): void
final protected function viewFile()
{
$file = $this->_apiWrapper( "marketplace/files/" . (int) \IPS\Request::i()->id, [ 'termFields' => 1 ], TRUE );
@@ -321,45 +367,52 @@ class _marketplace extends \IPS\Dispatcher\Controller
{
case 'apps':
/* Plugin or App? */
try
{
$app = \IPS\Application::load( $file['id'], 'app_marketplace_id' );
$updateAvailable = \count( $app->availableUpgrade() ) ? TRUE : FALSE;
}
catch( \OutOfRangeException $e )
{
$plugin = \IPS\Plugin::load( $file['id'], 'plugin_marketplace_id' );
if( $plugin->update_check_data )
{
$data = json_decode( $plugin->update_check_data, TRUE );
if( $data['longversion'] > $plugin->version_long )
{
$updateAvailable = TRUE;
}
}
}
break;
case 'themes':
$theme = \IPS\Theme::load( $file['id'], 'set_marketplace_id' );
if( $theme->update_data )
try
{
$data = json_decode( $theme->update_data, TRUE );
if( $data['longversion'] > $theme->long_version )
$app = \IPS\Application::load( $file['id'], 'app_marketplace_id' );
if( $file['latestLongVersion'] > $app->long_version )
{
$updateAvailable = TRUE;
}
}
break;
case 'languages':
$lang = \IPS\Lang::load( $file['id'], 'lang_marketplace_id' );
if( $lang->update_data )
catch( \OutOfRangeException $e )
{
$data = json_decode( $lang->update_data, TRUE );
if( $data['longversion'] > $lang->version_long )
$plugin = \IPS\Plugin::load( $file['id'], 'plugin_marketplace_id' );
if( $file['latestLongVersion'] > $plugin->version_long )
{
$updateAvailable = TRUE;
}
}
/* Legacy apps, or those with missing files */
catch( \UnexpectedValueException $e )
{
if ( mb_stristr( $e->getMessage(), 'Missing:' ) )
{
try
{
$appVersion = \IPS\Db::i()->select('app_long_version', 'core_applications', array( 'app_marketplace_id=?', $file['id'] ) )->first();
if( $file['latestLongVersion'] > $appVersion )
{
$updateAvailable = TRUE;
}
}
catch( \UnderflowException $e ) {}
}
}
break;
case 'themes':
$theme = \IPS\Theme::load( $file['id'], 'set_marketplace_id' );
if( $file['latestLongVersion'] > $theme->long_version )
{
$updateAvailable = TRUE;
}
break;
case 'languages':
$lang = \IPS\Lang::load( $file['id'], 'lang_marketplace_id' );
if( $file['latestLongVersion'] > $lang->version_long )
{
$updateAvailable = TRUE;
}
break;
}
$installed = TRUE;
@@ -391,7 +444,7 @@ class _marketplace extends \IPS\Dispatcher\Controller
*
* @return void
*/
final protected function buyFile(): void
final protected function buyFile()
{
\IPS\Session::i()->csrfCheck();
@@ -409,9 +462,7 @@ class _marketplace extends \IPS\Dispatcher\Controller
if ( $this->_token() )
{
$response = $this->_apiWrapper( "downloads/files/" . (int) \IPS\Request::i()->id . "/buy", NULL, TRUE, array(
'returnUri' => 'app=core&module=system&controller=ajax&do=dismissWindow'
) );
$response = $this->_apiWrapper( "marketplace/files/" . (int) \IPS\Request::i()->id . "/buy", NULL, TRUE, [] );
if ( isset( $response['errorCode'] ) )
{
@@ -433,7 +484,7 @@ class _marketplace extends \IPS\Dispatcher\Controller
*
* @return void
*/
final protected function fileChangelog(): void
final protected function fileChangelog()
{
$fileHistory = $this->_apiWrapper( "marketplace/files/" . (int) \IPS\Request::i()->id . '/history' );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'marketplace' )->fileChangelogGrid( $fileHistory );
@@ -444,7 +495,7 @@ class _marketplace extends \IPS\Dispatcher\Controller
*
* @return void
*/
final public function search(): void
final public function search()
{
\IPS\Output::i()->output =\IPS\Theme::i()->getTemplate('marketplace')->onboardSearch( \IPS\Request::i()->category, (bool) \IPS\Request::i()->compatible );
}
@@ -454,7 +505,7 @@ class _marketplace extends \IPS\Dispatcher\Controller
*
* @return void
*/
final protected function signout(): void
final protected function signout()
{
\IPS\Session::i()->csrfCheck();
\IPS\Db::i()->delete( 'core_marketplace_tokens', array( 'id=?', \IPS\Member::loggedIn()->member_id ) );
@@ -466,7 +517,7 @@ class _marketplace extends \IPS\Dispatcher\Controller
*
* @return void
*/
final protected function apiSearch(): void
final protected function apiSearch()
{
$queryString = [
'q' => \IPS\Request::i()->title,
@@ -500,14 +551,14 @@ class _marketplace extends \IPS\Dispatcher\Controller
{
if ( $results['totalResults'] == 1 )
{
\IPS\Output::i()->json([
\IPS\Output::i()->json( [
'id' => \intval( $results['results'][0]['objectId'] ),
'html' => \IPS\Theme::i()->getTemplate('marketplace')->fileCardMini( $this->_api( "downloads/files/" . (int) $results['results'][0]['objectId'] ) )
]);
] );
}
else
{
\IPS\Output::i()->sendOutput( '' );
\IPS\Output::i()->json( [ 'error' => \IPS\Member::loggedIn()->language()->addToStack( 'no_results' ) ], 404 );
}
}
else
@@ -525,16 +576,16 @@ class _marketplace extends \IPS\Dispatcher\Controller
/* Nothing compatible */
if( !$results['totalResults'] )
{
\IPS\Output::i()->sendOutput( \IPS\Member::loggedIn()->language()->addToStack( 'no_results' ) );
\IPS\Output::i()->json( [ 'error' => \IPS\Member::loggedIn()->language()->addToStack( 'no_results' ) ], 404 );
}
else
{
\IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate('marketplace')->fileCardList( $results['results'] ) );
\IPS\Output::i()->json( [ 'html' => \IPS\Theme::i()->getTemplate('marketplace')->fileCardList( $results['results'] ) ] );
}
}
else
{
\IPS\Output::i()->sendOutput( \IPS\Member::loggedIn()->language()->addToStack( 'no_results' ) );
\IPS\Output::i()->json( [ 'error' => \IPS\Member::loggedIn()->language()->addToStack( 'no_results' ) ], 404 );
}
}
}
@@ -544,15 +595,17 @@ class _marketplace extends \IPS\Dispatcher\Controller
*
* @return void
*/
final protected function apiLookup(): void
final protected function apiLookup()
{
try
{
\IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate('marketplace')->fileCardMini( $this->_api( "downloads/files/" . (int) \IPS\Request::i()->id ) ) );
\IPS\Output::i()->json( [
'html' => \IPS\Theme::i()->getTemplate('marketplace')->fileCardMini( $this->_api( "downloads/files/" . (int) \IPS\Request::i()->id ) )
] );
}
catch ( \Exception $e )
{
\IPS\Output::i()->sendOutput( '' );
\IPS\Output::i()->json( [ 'error' => \IPS\Member::loggedIn()->language()->addToStack( 'no_results' ) ], 403 );
}
}
@@ -590,12 +643,13 @@ class _marketplace extends \IPS\Dispatcher\Controller
* @param bool $authenticate Send Authentication data
* @param array|null $data POST/PUT Data
* @param int $timeout Custom timeout value for request
* @param bool $retry Retried request after token expire
* @return array JSON decoded response
*
* @throws \RuntimeException
* @throws \UnexpectedValueException
*/
final protected function _api( string $endpoint, array $queryString = NULL, bool $authenticate = FALSE, array $data = NULL, int $timeout = \IPS\DEFAULT_REQUEST_TIMEOUT ): array
final protected function _api( string $endpoint, array $queryString = NULL, bool $authenticate = FALSE, array $data = NULL, int $timeout = \IPS\DEFAULT_REQUEST_TIMEOUT, bool $retry = FALSE ): array
{
$token = $authenticate ? ( $this->_token() ?? \IPS\MARKETPLACE_GUEST_TOKEN ) : \IPS\MARKETPLACE_GUEST_TOKEN;
$url = \IPS\Http\Url::external( \IPS\MARKETPLACE_URL . '/api/' . $endpoint )->setQueryString( 'ips_version', \IPS\Application::load('core')->long_version );
@@ -607,13 +661,32 @@ class _marketplace extends \IPS\Dispatcher\Controller
$request = $url ->request( $timeout )
->setHeaders( array( 'Authorization' => "Bearer {$token}" ) );
if ( $data === NULL )
try
{
$response = $request->get();
if ( $data === NULL )
{
$response = $request->get();
}
else
{
$response = $request->post( $data );
}
}
else
catch( \IPS\Http\Request\Exception $e )
{
$response = $request->post( $data );
throw new \UnexpectedValueException();
}
$json = $response->decodeJson( TRUE );
if( !$retry AND $response->httpResponseCode == 401 AND !empty( $json['errorCode'] ) AND \in_array( $json['errorCode'], [ '1S290/F', '3S290/9' ] ) )
{
/* Delete token */
$this->_token = NULL;
\IPS\Db::i()->delete( 'core_marketplace_tokens', array( 'id=?', \IPS\Member::loggedIn()->member_id ) );
/* Retry request */
return $this->_api( $endpoint, $queryString, $authenticate, $data, $timeout, TRUE );
}
if( $response->httpResponseCode != 200 )
@@ -621,8 +694,6 @@ class _marketplace extends \IPS\Dispatcher\Controller
throw new \RuntimeException( (string) $response, $response->httpResponseCode );
}
$json = $response->decodeJson( TRUE );
if( $json === NULL )
{
throw new \UnexpectedValueException();
@@ -656,6 +727,12 @@ class _marketplace extends \IPS\Dispatcher\Controller
return $hold;
}
/**
* Calculate renewal term string from API response
*
* @param array $renewalTerm
* @return string
*/
final protected function _calculateRenewalTerm( array $renewalTerm ): string
{
$lang = \IPS\Member::loggedIn()->language();
@@ -727,8 +804,17 @@ class _marketplace extends \IPS\Dispatcher\Controller
try
{
$response = \IPS\Http\Url::external( \IPS\MARKETPLACE_URL . '/api/core/me' )->request()->setHeaders( array( 'Authorization' => "Bearer {$token}" ) )->get();
$memberData = $response->decodeJson();
$authenticatedMember = $memberData['name'];
$data = $response->decodeJson();
/* Revoked Token, delete our local token and re-set the header */
if( !empty( $data['errorCode'] ) AND \in_array( $data['errorCode'], [ '1S290/F', '3S290/9' ] ) )
{
/* Delete token */
$this->_token = NULL;
\IPS\Db::i()->delete( 'core_marketplace_tokens', array( 'id=?', \IPS\Member::loggedIn()->member_id ) );
return $this->_customHeader( $title, $file, $categoryIds );
}
$authenticatedMember = $data['name'];
}
catch ( \Exception $e )
{
@@ -777,7 +863,7 @@ class _marketplace extends \IPS\Dispatcher\Controller
* @param array $file REST API file array
* @param string $temporaryFile Path to temporary file for application
*/
final protected function _installApplication( array $file, string $temporaryFile ): void
final protected function _installApplication( array $file, string $temporaryFile )
{
/* Test the phar */
rename( $temporaryFile, $temporaryFile . '.tar' );
@@ -813,6 +899,14 @@ class _marketplace extends \IPS\Dispatcher\Controller
/* New install */
$_type = 'install';
}
catch( \UnexpectedValueException $e )
{
/* Catch exception if it is a legacy application that needs upgrading */
if ( !mb_stristr( $e->getMessage(), 'Missing:' ) )
{
throw $e;
}
}
}
if ( \IPS\CIC2 )
@@ -860,11 +954,10 @@ class _marketplace extends \IPS\Dispatcher\Controller
* @param array $file REST API file array
* @param string $temporaryFile Path to temporary language XML file
*/
final protected function _installLanguage( array $file, string $temporaryFile ): void
final protected function _installLanguage( array $file, string $temporaryFile )
{
/* Already installed? */
$xml = new \IPS\Xml\XMLReader;
$xml->open( $temporaryFile );
$xml = \IPS\Xml\XMLReader::safeOpen( $temporaryFile );
if ( !@$xml->read() )
{
throw new \UnexpectedValueException;
@@ -891,11 +984,10 @@ class _marketplace extends \IPS\Dispatcher\Controller
* @param array $file REST API file array
* @param string $temporaryFile Path to temporary plugin XML file
*/
final protected function _installPlugin( array $file, string $temporaryFile ): void
final protected function _installPlugin( array $file, string $temporaryFile )
{
/* Already installed? */
$xml = new \IPS\Xml\XMLReader;
$xml->open( $temporaryFile );
$xml = \IPS\Xml\XMLReader::safeOpen( $temporaryFile );
if ( !@$xml->read() )
{
throw new \UnexpectedValueException;
@@ -930,10 +1022,9 @@ class _marketplace extends \IPS\Dispatcher\Controller
* @param array $file REST API file array
* @param string $temporaryFile Path to temporary theme XML file
*/
final protected function _installTheme( array $file, string $temporaryFile ): void
final protected function _installTheme( array $file, string $temporaryFile )
{
$xml = new \IPS\Xml\XMLReader;
$xml->open( $temporaryFile );
$xml = \IPS\Xml\XMLReader::safeOpen( $temporaryFile );
if ( !@$xml->read() )
{
throw new \UnexpectedValueException;
@@ -984,7 +1075,16 @@ class _marketplace extends \IPS\Dispatcher\Controller
catch( \OutOfRangeException $e )
{
$categories = [ 'apps' => [], 'themes' => [], 'languages' => [], 'all' => [] ];
$response = $this->_api( 'marketplace/categories', NULL, FALSE, NULL, 2 );
try
{
$response = $this->_api( 'marketplace/categories', NULL, FALSE, NULL, 2 );
}
catch( \RuntimeException | \UnexpectedValueException $e )
{
return $categories;
}
foreach( $response['results'] as $cat )
{
if( isset( $categories[ $cat['resourceType'] ] ) )
@@ -1007,7 +1107,7 @@ class _marketplace extends \IPS\Dispatcher\Controller
* @param int $ttl Time to live in minutes
* @return void
*/
final protected function _setDatastoreCache( string $name, array $content, int $ttl ): void
final protected function _setDatastoreCache( string $name, array $content, int $ttl )
{
$ttlSeconds = $ttl * 60;
$variableName = 'marketplace_' . $name . '_' . md5( $this->_token() ?? \IPS\MARKETPLACE_GUEST_TOKEN );
@@ -1131,13 +1231,12 @@ class _marketplace extends \IPS\Dispatcher\Controller
/**
* Handler for Core update check functionality
*
* @param int $fileId Marketplace ID
* @param int $currentVersion Current Long Version
* @param array $files Array of files, key is Marketplace ID, value is current version
* @return array
*/
final public function _updateCheck( int $fileId, int $currentVersion ):? array
final public function _updateCheck( array $files ):? array
{
return $this->_api("marketplace/files/{$fileId}/updatecheck", [ 'version' => $currentVersion ] );
return $this->_api("marketplace/files/versions", [ 'files' => $files ] );
}
/**
@@ -1150,7 +1249,7 @@ class _marketplace extends \IPS\Dispatcher\Controller
{
$valid = TRUE;
$licenseData = \IPS\IPS::licenseKey();
if ( !$licenseData or strtotime( $licenseData['expires'] ) < time() )
if ( !$licenseData or ( $licenseData['expires'] !== NULL AND strtotime( $licenseData['expires'] ) < time() ) )
{
$valid = FALSE;
}
@@ -32,10 +32,16 @@ class _onboard extends \IPS\Dispatcher\Controller
*
* @return void
*/
public function manage(): void
final public function manage()
{
/* Has onboarding been completed? */
if( \IPS\Settings::i()->mp_onboard_complete )
{
\IPS\Output::i()->error( 'marketplace_onboard_already_complete', '2C420/1', 403, '' );
}
$steps = [];
if ( array_filter( array_keys( \IPS\Application::applications() ), function( $k ) { return !\in_array( $k, \IPS\Application::$ipsApps ); } ) )
if ( array_filter( array_keys( \IPS\Application::applications() ), function( $k ) { return !\in_array( $k, \IPS\IPS::$ipsApps ); } ) )
{
$steps['marketplace_onboard_applications'] = array( $this, '_applications' );
}
@@ -71,7 +77,7 @@ class _onboard extends \IPS\Dispatcher\Controller
* @param mixed $data Wizard data
* @return mixed
*/
public function _applications( $data )
final public function _applications( $data )
{
if ( \IPS\Request::i()->match_apps )
{
@@ -90,7 +96,7 @@ class _onboard extends \IPS\Dispatcher\Controller
$rows = [];
foreach ( \IPS\Application::applications() as $application )
{
if ( !\in_array( $application->directory, \IPS\Application::$ipsApps ) )
if ( !\in_array( $application->directory, \IPS\IPS::$ipsApps ) )
{
$rows[ $application->directory ] = \IPS\Theme::i()->getTemplate('marketplace')->onboardRow( $application->directory, $application->marketplace_id, 'apps', $application->_title, $application->version, $application->author, $application->website, 'apps' );
}
@@ -111,7 +117,7 @@ class _onboard extends \IPS\Dispatcher\Controller
* @param mixed $data Wizard data
* @return mixed
*/
public function _plugins( $data )
final public function _plugins( $data )
{
if ( \IPS\Request::i()->match_plugins )
{
@@ -148,7 +154,7 @@ class _onboard extends \IPS\Dispatcher\Controller
* @param mixed $data Wizard data
* @return mixed
*/
public function _themes( $data )
final public function _themes( $data )
{
if ( \IPS\Request::i()->match_themes )
{
@@ -188,7 +194,7 @@ class _onboard extends \IPS\Dispatcher\Controller
* @param mixed $data Wizard data
* @return mixed
*/
public function _languages( $data )
final public function _languages( $data )
{
if ( \IPS\Request::i()->match_languages )
{
@@ -228,8 +234,11 @@ class _onboard extends \IPS\Dispatcher\Controller
* @param mixed $data Wizard data
* @return mixed
*/
public function _complete()
final public function _complete()
{
/* Set onboard complete flag */
\IPS\Settings::i()->changeValues( array( 'mp_onboard_complete' => 1 ) );
\IPS\core\AdminNotification::remove( 'core', 'ConfigurationError', 'marketplaceSetup' );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal('app=core&module=marketplace&controller=marketplace') );
}
@@ -78,7 +78,7 @@ class _devices extends \IPS\Dispatcher\Controller
{
$oauthClients = \IPS\Api\OAuthClient::roots();
$apps = array();
foreach ( \IPS\Db::i()->select( '*', 'core_oauth_server_access_tokens', array( 'member_id=? AND device_key=?', \IPS\Member::loggedIn()->member_id, $device->device_key ), 'issued DESC' ) as $accessToken )
foreach ( \IPS\Db::i()->select( '*', 'core_oauth_server_access_tokens', array( 'member_id=? AND device_key=?', $member->member_id, $device->device_key ), 'issued DESC' ) as $accessToken )
{
if ( isset( $oauthClients[ $accessToken['client_id'] ] ) )
{
@@ -269,7 +269,7 @@ class _devices extends \IPS\Dispatcher\Controller
$device->login_key = NULL;
$device->save();
\IPS\Db::i()->delete( 'core_oauth_server_access_tokens', array( 'member_id=? AND device_key=?', $device->member_id, $device->device_key ) );
\IPS\Db::i()->update( 'core_oauth_server_access_tokens', array( 'status' => 'revoked' ), array( 'member_id=? AND device_key=?', $device->member_id, $device->device_key ) );
$member->logHistory( 'core', 'login', array( 'type' => 'logout', 'device' => $device->device_key ) );
\IPS\Session::i()->log( 'acplog__members_disabled_autologin', array( $member->name => FALSE ) );
File diff suppressed because it is too large. Load diff
@@ -142,13 +142,13 @@ class _ban extends \IPS\Dispatcher\Controller
/* Build form */
$form = new \IPS\Helpers\Form();
$form->add( new \IPS\Helpers\Form\Select( 'ban_type', $current['ban_type'], TRUE, array( 'options' => array(
$form->add( new \IPS\Helpers\Form\Select( 'ban_type', $current ? $current['ban_type'] : NULL , TRUE, array( 'options' => array(
'ip' => 'ban_filter_ip_select',
'email' => 'ban_filter_email_select',
'name' => 'ban_filter_name_select'
) ) ) );
$form->add( new \IPS\Helpers\Form\Text( 'ban_content', $current['ban_content'], TRUE ) );
$form->add( new \IPS\Helpers\Form\Text( 'ban_reason', $current['ban_reason'] ) );
$form->add( new \IPS\Helpers\Form\Text( 'ban_content', $current ? $current['ban_content'] : "", TRUE ) );
$form->add( new \IPS\Helpers\Form\Text( 'ban_reason', $current ? $current['ban_reason'] : "" ) );
/* Handle submissions */
if ( $values = $form->values() )
@@ -64,18 +64,6 @@ class _notifications extends \IPS\Dispatcher\Controller
)
);
};
$methodTable = new \IPS\Helpers\Table\Custom( array( 'inline' => array( 'method' => \IPS\Member::loggedIn()->language()->addToStack('member_notifications_inline') ) ), \IPS\Http\Url::internal( 'app=core&module=membersettings&controller=notifications', 'admin' ) );
$methodTable->langPrefix = 'notificationsettings_';
$methodTable->rowButtons = function( $row, $k ) {
return array(
'edit' => array(
'title' => 'edit',
'link' => \IPS\Http\Url::internal( 'app=core&module=membersettings&controller=notifications&do=method', 'admin' )->setQueryString( 'id', $k ),
'icon' => 'pencil'
)
);
};
if( !\IPS\CIC )
{
@@ -100,7 +88,13 @@ class _notifications extends \IPS\Dispatcher\Controller
}
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('notifications');
\IPS\Output::i()->output = $typeTable . $methodTable;
if ( ! \IPS\Notification::canUseWebPush() )
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('global')->message( 'acp_notifications_cannot_use_web_push', 'general' );
}
\IPS\Output::i()->output .= $typeTable;
}
/**
@@ -136,7 +130,8 @@ class _notifications extends \IPS\Dispatcher\Controller
{
\IPS\Task::queue( 'core', 'PruneLargeTable', array(
'table' => 'core_follow',
'where' => array( 'follow_member_id IN(?)', \IPS\Db::i()->select( 'member_id', 'core_members', array( 'last_activity < ?', \IPS\DateTime::create()->sub( new \DateInterval( 'P' . $values['prune_follows'] . 'D' ) )->getTimestamp() ) ) ),
'where' => array( 'follow_app!=? AND follow_area!=? AND follow_member_id IN(?)', 'core', 'member', \IPS\Db::i()->select( 'member_id', 'core_members', array( 'last_activity < ?', \IPS\DateTime::create()->sub( new \DateInterval( 'P' . $values['prune_follows'] . 'D' ) )->getTimestamp() ) ) ),
'setting' => 'prune_follows',
'deleteJoin' => array(
'column' => 'member_id',
'table' => 'core_members',
@@ -187,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 and !\in_array( 'push', $option['disabled'] ) ) or !\in_array( 'email', $option['disabled'] ) )
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'] ) )
{
$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" ),
@@ -212,8 +207,16 @@ class _notifications extends \IPS\Dispatcher\Controller
}
}
}
foreach ( array( 'inline', 'push', 'email' ) as $k )
if ( \IPS\Notification::canUseWebPush() )
{
$types = array( 'inline', 'push', 'email' );
}
else
{
$types = array( 'inline', 'email' );
}
foreach ( $types as $k )
{
if ( !\in_array( $k, $option['disabled'] ) )
{
@@ -278,7 +281,7 @@ class _notifications extends \IPS\Dispatcher\Controller
}
}
if ( !\in_array( 'inline', $option['disabled'] ) or ( \IPS\Settings::i()->mobile_app_id and !\in_array( 'push', $option['disabled'] ) ) or !\in_array( 'email', $option['disabled'] ) )
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'] ) )
{
$row = array(
'notification_key' => $key,
@@ -293,7 +296,7 @@ class _notifications extends \IPS\Dispatcher\Controller
{
if ( $k === 'push' )
{
if ( !\IPS\Settings::i()->mobile_app_id )
if ( !\IPS\Settings::i()->mobile_app_id && !\IPS\Notification::webPushEnabled() )
{
continue;
}
@@ -349,36 +352,6 @@ class _notifications extends \IPS\Dispatcher\Controller
\IPS\Output::i()->output = $form;
}
/**
* Notification List Settings
*
* @return void
*/
protected function method()
{
$form = new \IPS\Helpers\Form;
$form->addHeader('notification_preferences');
$form->add( new \IPS\Helpers\Form\Radio( 'notification_prefs_sound', \IPS\Settings::i()->notification_prefs_sound ? 'default' : 'optional', FALSE, array( 'options' => array(
'default' => 'admin_notification_pref_default',
'optional' => 'admin_notification_pref_optional',
) ), NULL, NULL, NULL, 'notification_prefs_sound' ) );
if ( $values = $form->values() )
{
$values['notification_prefs_sound'] = ( $values['notification_prefs_sound'] === 'default' );
$form->saveAsSettings( $values );
\IPS\Session::i()->log( 'acplog__notification_settings' );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=membersettings&controller=notifications' ), 'saved' );
}
\IPS\Output::i()->breadcrumb[] = array( \IPS\Http\Url::internal( 'app=core&module=membersettings&controller=notifications' ), \IPS\Member::loggedIn()->language()->addToStack( 'menu__core_membersettings_notifications' ) );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('notificationsettings');
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('global')->block( 'notificationsettings', $form, FALSE );
}
/**
* Member Auto Follow Preferences
*
@@ -117,7 +117,7 @@ class _profilesettings extends \IPS\Dispatcher\Controller
'options' => array( 'public' => 'profile_birthday_type_public', 'private' => 'profile_birthday_type_private', 'none' => 'profile_birthday_type_none' )
), NULL, NULL, NULL, 'profile_birthday_type' ) );
if( !\IPS\CIC AND \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'profiles', 'member_history_prune' ) )
if( !\IPS\CIC AND \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'membersettings', 'member_history_prune' ) )
{
$form->addHeader( 'profile_member_history' );
$form->add( new \IPS\Helpers\Form\Interval( 'prune_member_history', \IPS\Settings::i()->prune_member_history, FALSE, array( 'valueAs' => \IPS\Helpers\Form\Interval::DAYS, 'unlimited' => 0, 'unlimitedLang' => 'never' ), NULL, \IPS\Member::loggedIn()->language()->addToStack('after'), NULL, 'prune_member_history' ) );
@@ -214,6 +214,7 @@ class _profilesettings extends \IPS\Dispatcher\Controller
\IPS\Task::queue( 'core', 'PruneLargeTable', array(
'table' => 'core_member_history',
'where' => array( 'log_date < ?', \IPS\DateTime::create()->sub( new \DateInterval( 'P' . $values['prune_member_history'] . 'D' ) )->getTimestamp() ),
'setting' => 'prune_member_history',
), 4 );
}
@@ -222,6 +223,7 @@ class _profilesettings extends \IPS\Dispatcher\Controller
\IPS\Task::queue( 'core', 'PruneLargeTable', array(
'table' => 'core_members_known_ip_addresses',
'where' => array( 'last_seen < ?', \IPS\DateTime::create()->sub( new \DateInterval( 'P' . $values['prune_known_ips'] . 'D' ) )->getTimestamp() ),
'setting' => 'prune_known_ips',
), 4 );
}
@@ -230,6 +232,7 @@ class _profilesettings extends \IPS\Dispatcher\Controller
\IPS\Task::queue( 'core', 'PruneLargeTable', array(
'table' => 'core_members_known_devices',
'where' => array( 'last_seen < ?', \IPS\DateTime::create()->sub( new \DateInterval( 'P' . $values['prune_known_devices'] . 'D' ) )->getTimestamp() ),
'setting' => 'prune_known_devices',
), 4 );
}
@@ -238,6 +241,7 @@ class _profilesettings extends \IPS\Dispatcher\Controller
\IPS\Task::queue( 'core', 'PruneLargeTable', array(
'table' => 'core_item_markers',
'where' => array( 'item_member_id IN(?)', \IPS\Db::i()->select( 'member_id', 'core_members', array( 'last_activity < ?', \IPS\DateTime::create()->sub( new \DateInterval( 'P' . $values['prune_item_markers'] . 'D' ) )->getTimestamp() ) ) ),
'setting' => 'prune_item_markers',
'deleteJoin' => array(
'column' => 'member_id',
'table' => 'core_members',
@@ -1,258 +0,0 @@
<?php
/**
* @brief ranks
* @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 Apr 2013
*/
namespace IPS\core\modules\admin\membersettings;
/* 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;
}
/**
* ranks
*/
class _ranks extends \IPS\Dispatcher\Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'ranks_manage' );
parent::execute();
}
/**
* Manage
*
* @return void
*/
protected function manage()
{
/* Create the table */
$table = new \IPS\Helpers\Table\Db( 'core_member_ranks', \IPS\Http\Url::internal( 'app=core&module=membersettings&controller=ranks' ) );
/* Columns */
$table->joins = array(
array( 'select' => 'w.word_custom', 'from' => array( 'core_sys_lang_words', 'w' ), 'where' => "w.word_key=CONCAT( 'core_member_rank_', core_member_ranks.id ) AND w.lang_id=" . \IPS\Member::loggedIn()->language()->id )
);
$table->include = array( 'word_custom', 'posts', 'pips' );
$table->langPrefix = 'member_ranks_';
$table->mainColumn = 'title';
$table->noSort = array( 'pips' );
$table->parsers = array(
'pips' => function( $val, $row )
{
if ( $row['use_icon'] and $row['icon'] )
{
return "<img src='" . \IPS\File::get( 'core_Theme', $row['icon'] )->url . "' alt=''>";
}
else
{
return str_repeat("<span class='ipsPip'></span>", \intval($val) );
}
}
);
$table->sortBy = $table->sortBy ?: 'posts';
$table->sortDirection = $table->sortDirection ?: 'asc';
/* Root Buttons */
\IPS\Output::i()->sidebar['actions'] = array(
'add' => array(
'primary' => TRUE,
'icon' => 'plus',
'title' => 'member_ranks_add',
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('member_ranks_add') ),
'link' => \IPS\Http\Url::internal( 'app=core&module=membersettings&controller=ranks&do=form' ),
)
);
/* Row buttons */
$table->rowButtons = function( $row )
{
$return = array();
$return['edit'] = array(
'icon' => 'pencil',
'title' => 'edit',
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('edit') ),
'link' => \IPS\Http\Url::internal( 'app=core&module=membersettings&controller=ranks&do=form&id=' ) . $row['id'],
);
$return['delete'] = array(
'icon' => 'times-circle',
'title' => 'delete',
'link' => \IPS\Http\Url::internal( 'app=core&module=membersettings&controller=ranks&do=delete&id=' ) . $row['id'],
'data' => array( 'delete' => '' ),
);
return $return;
};
/* The incredibly complex search code */
$table->quickSearch = 'word_custom';
/* Show a message about the setting to allow members to set their own */
switch ( \IPS\Settings::i()->post_titlechange )
{
case -1:
$message = \IPS\Member::loggedIn()->language()->addToStack('rankown_none');
break;
case 0:
$message = \IPS\Member::loggedIn()->language()->addToStack('rankown_always');
break;
default:
$message = \IPS\Member::loggedIn()->language()->addToStack( 'rankown_limit', FALSE, array( 'pluralize' => array( \IPS\Settings::i()->post_titlechange ) ) );
break;
}
$message .= ' <a href="' . \IPS\Http\Url::internal( 'app=core&module=membersettings&controller=ranks&do=settings' ) . '" data-ipsDialog data-ipsDialog-title="' . \IPS\Member::loggedIn()->language()->addToStack('change') . '">' . \IPS\Member::loggedIn()->language()->addToStack('change') . '</a>';
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->message( $message, 'information', NULL, FALSE );
/* Display */
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('ranks');
\IPS\Output::i()->output .= (string) $table;
}
/**
* Add/Edit Rank
*
* @return void
*/
public function form()
{
$current = NULL;
if ( \IPS\Request::i()->id )
{
$current = \IPS\Db::i()->select( '*', 'core_member_ranks', array( 'id=?', \IPS\Request::i()->id ) )->first();
}
/* Build form */
$form = new \IPS\Helpers\Form();
$form->add( new \IPS\Helpers\Form\Translatable( 'member_ranks_word_custom', NULL, TRUE, array( 'app' => 'core', 'key' => ( $current ? "core_member_rank_{$current['id']}" : NULL ) ) ) );
$form->add( new \IPS\Helpers\Form\Number( 'member_ranks_posts', $current ? $current['posts'] : 0, TRUE, array( 'min' => 0 ) ) );
$form->add( new \IPS\Helpers\Form\Radio( 'member_use_icon', $current ? $current['use_icon'] : NULL, FALSE, array(
'parse' => 'raw',
'options' => array( 0 => \IPS\Member::loggedIn()->language()->addToStack('member_ranks_pip_number', FALSE, array( 'htmlsprintf' => array( "<span class='ipsPip'></span>" ) ) ), 1 => \IPS\Member::loggedIn()->language()->addToStack('member_ranks_pip_icon') ),
'toggles' => array( 0 => array('member_ranks_pips'), 1 => array('member_ranks_icon') )
) ) );
/* Pip Icon */
$pip = NULL;
if ( $current AND $current['icon'] )
{
$pip = \IPS\File::get( 'core_Theme', $current['icon'] );
}
$form->add( new \IPS\Helpers\Form\Number( 'member_ranks_num_pips' , $current ? $current['pips'] : 0, FALSE, array( 'min' => 0, 'max' => 100 ), NULL, NULL, NULL, 'member_ranks_pips' ) );
$form->add( new \IPS\Helpers\Form\Upload( 'member_ranks_icon', $pip, FALSE, array( 'image' => array( 'maxWidth' => 140, 'maxHeight' => 140 ), 'storageExtension' => 'core_Theme' ), NULL, NULL, NULL, 'member_ranks_icon' ) );
if ( $values = $form->values() )
{
$save = array(
'posts' => \intval( $values['member_ranks_posts'] ),
'pips' => \intval( $values['member_ranks_num_pips'] ),
'use_icon' => \intval( $values['member_use_icon'] ),
'icon' => '',
);
if ( $values['member_ranks_icon'] instanceof \IPS\File )
{
$save['icon'] = (string) $values['member_ranks_icon'];
}
if ( $current )
{
\IPS\Db::i()->update( 'core_member_ranks', $save, array( 'id=?', $current['id'] ) );
$id = $current['id'];
\IPS\Session::i()->log( 'acplogs__rank_edited', array( $save['posts'] => FALSE ) );
}
else
{
$id = \IPS\Db::i()->insert( 'core_member_ranks', $save );
\IPS\Session::i()->log( 'acplogs__rank_created', array( $save['posts'] => FALSE ) );
}
\IPS\Lang::saveCustom( 'core', "core_member_rank_{$id}", $values['member_ranks_word_custom'] );
unset( \IPS\Data\Store::i()->ranks );
/* Clear guest page caches */
\IPS\Data\Cache::i()->clearAll();
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=membersettings&controller=ranks' ), 'saved' );
}
/* Display */
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->block( $current ? "core_member_rank_{$current['id']}" : 'add', $form, FALSE );
}
/**
* Delete
*
* @return void
*/
public function delete()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'ranks_manage' );
/* Make sure the user confirmed the deletion */
\IPS\Request::i()->confirmedDelete();
try
{
$current = \IPS\Db::i()->select( '*', 'core_member_ranks', array( 'id=?', \IPS\Request::i()->id ) )->first();
\IPS\Session::i()->log( 'acplogs__rank_deleted', array( $current['posts'] => FALSE ) );
\IPS\Db::i()->delete( 'core_member_ranks', array( 'id=?', \IPS\Request::i()->id ) );
\IPS\Lang::deleteCustom( 'core', 'core_member_rank_' . \IPS\Request::i()->id );
}
catch ( \UnderflowException $e ) { }
unset( \IPS\Data\Store::i()->ranks );
/* Clear guest page caches */
\IPS\Data\Cache::i()->clearAll();
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=membersettings&controller=ranks' ) );
}
/**
* Settings
*
* @return void
*/
public function settings()
{
$form = new \IPS\Helpers\Form;
$form->add( new \IPS\Helpers\Form\Number( 'post_titlechange', \IPS\Settings::i()->post_titlechange, FALSE, array( 'unlimited' => -1, 'unlimitedLang' => 'never_allow' ) ) );
if ( $form->values() )
{
$form->saveAsSettings();
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=membersettings&controller=ranks' ), 'saved' );
}
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->block( 'ranks', $form, FALSE );
}
}
@@ -156,7 +156,7 @@ class _reputation extends \IPS\Node\Controller
/* Build Form */
$form = new \IPS\Helpers\Form();
$form->add( new \IPS\Helpers\Form\YesNo( 'reputation_enabled', \IPS\Settings::i()->reputation_enabled, FALSE, array( 'togglesOn' => array( 'reputation_point_types', 'reputation_protected_groups', 'reputation_can_self_vote', 'reputation_highlight', 'reputation_show_profile', 'overall_reaction_count' ) ) ) );
$form->add( new \IPS\Helpers\Form\Select( 'reputation_protected_groups', explode( ',', \IPS\Settings::i()->reputation_protected_groups ), FALSE, array( 'options' => \IPS\Member\Group::groups( TRUE, FALSE ), 'parse' => 'normal', 'multiple' => TRUE ), NULL, NULL, NULL, 'reputation_protected_groups' ) );
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'reputation_protected_groups', explode( ',', \IPS\Settings::i()->reputation_protected_groups ), FALSE, array( 'options' => \IPS\Member\Group::groups( TRUE, FALSE ), 'parse' => 'normal', 'multiple' => TRUE ), NULL, NULL, NULL, 'reputation_protected_groups' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'reputation_can_self_vote', \IPS\Settings::i()->reputation_can_self_vote, FALSE, array(), NULL, NULL, NULL, 'reputation_can_self_vote' ) );
$form->add( new \IPS\Helpers\Form\Number( 'reputation_highlight', \IPS\Settings::i()->reputation_highlight, FALSE, array( 'unlimited' => 0, 'unlimitedLang' => 'never' ), NULL, \IPS\Member::loggedIn()->language()->addToStack('reputation_highlight_prefix'), \IPS\Member::loggedIn()->language()->addToStack('reputation_highlight_suffix'), 'reputation_highlight' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'reputation_show_profile', \IPS\Settings::i()->reputation_show_profile, FALSE, array(), NULL, NULL, NULL, 'reputation_show_profile' ) );
@@ -186,7 +186,7 @@ class _reputation extends \IPS\Node\Controller
$form = new \IPS\Helpers\Form();
$form->add( new \IPS\Helpers\Form\YesNo( 'reputation_leaderboard_on', \IPS\Settings::i()->reputation_leaderboard_on, FALSE, array( 'togglesOn' => array( 'reputation_leaderboard_default_tab', 'form_header_leaderboard_tabs_leaderboard', 'reputation_show_days_won_trophy', 'reputation_timezone', 'form_header_leaderboard_tabs_members', 'reputation_top_members_overview', 'reputation_overview_max_members', 'reputation_top_members_filters', 'reputation_max_members', 'leaderboard_excluded_groups' ) ), NULL, NULL, NULL, 'reputation_leaderboard_on' ) );
$form->add( new \IPS\Helpers\Form\Radio( 'reputation_leaderboard_default_tab', \IPS\Settings::i()->reputation_leaderboard_default_tab, FALSE, array( 'options' => array( 'leaderboard' => 'leaderboard_tabs_leaderboard', 'history' => 'leaderboard_tabs_history', 'members' => 'leaderboard_tabs_members' ) ), NULL, NULL, NULL, 'reputation_leaderboard_default_tab' ) );
$form->add( new \IPS\Helpers\Form\Select( 'leaderboard_excluded_groups', explode( ',', \IPS\Settings::i()->leaderboard_excluded_groups ), FALSE, array( 'options' => \IPS\Member\Group::groups( TRUE, FALSE ), 'parse' => 'normal', 'multiple' => TRUE ), NULL, NULL, NULL, 'leaderboard_excluded_groups' ) );
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'leaderboard_excluded_groups', explode( ',', \IPS\Settings::i()->leaderboard_excluded_groups ), FALSE, array( 'options' => \IPS\Member\Group::groups( TRUE, FALSE ), 'parse' => 'normal', 'multiple' => TRUE ), NULL, NULL, NULL, 'leaderboard_excluded_groups' ) );
$form->addHeader('leaderboard_tabs_leaderboard');
$form->add( new \IPS\Helpers\Form\YesNo( 'reputation_show_days_won_trophy', \IPS\Settings::i()->reputation_show_days_won_trophy, FALSE, array( 'togglesOn' => array() ), NULL, NULL, NULL, 'reputation_show_days_won_trophy' ) );
$form->add( new \IPS\Helpers\Form\Timezone( 'reputation_timezone', \IPS\Settings::i()->reputation_timezone, FALSE, array(), NULL, NULL, NULL, 'reputation_timezone' ) );
@@ -411,7 +411,7 @@ class _reputation extends \IPS\Node\Controller
* @param string $lastUsedTab The tab last used in the form
* @return void
*/
protected function _afterSave( \IPS\Node\Model $old = NULL, \IPS\Node\Model $new, $lastUsedTab = FALSE )
protected function _afterSave( ?\IPS\Node\Model $old, \IPS\Node\Model $new, $lastUsedTab = FALSE )
{
if( \IPS\Request::i()->isAjax() )
{
@@ -159,7 +159,7 @@ class _menu extends \IPS\Dispatcher\Controller
'toggles' => array( 1 => array( 'menu_manager_access' ) )
), NULL, NULL, NULL, 'menu_manager_access_type' ) );
}
$form->add( new \IPS\Helpers\Form\Select( 'menu_manager_access', $existing ? ( $existing['permissions'] == '*' ? '*' : explode( ',', $existing['permissions'] ) ) : '*', NULL, array( 'multiple' => TRUE, 'options' => $groups, 'unlimited' => '*', 'unlimitedLang' => 'everyone' ), NULL, NULL, NULL, 'menu_manager_access' ) );
$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;
@@ -34,6 +34,11 @@ class _mobile extends \IPS\Dispatcher\Controller
*/
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();
}
@@ -117,6 +122,12 @@ class _mobile extends \IPS\Dispatcher\Controller
}
$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' ) );
@@ -245,7 +256,7 @@ class _mobile extends \IPS\Dispatcher\Controller
if ( $file )
{
$image = \IPS\Image::create( $file->contents() );
if ( $image->width !== $image->height )
if ( !$image->isSquare() )
{
throw new \DomainException('mobile_app_listing_logo_square');
}
@@ -288,7 +299,7 @@ class _mobile extends \IPS\Dispatcher\Controller
$langauges = ['en'];
}
}
$form->add( new \IPS\Helpers\Form\Select( 'mobile_app_listing_languages', $existing ? explode( ',', $existing['languages'] ): ['en'], TRUE, array( 'multiple' => TRUE, 'options' => $langaugeOptions ) ) );
$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 )
{
@@ -197,7 +197,9 @@ class _spam extends \IPS\Dispatcher\Controller
$licenseData = \IPS\IPS::licenseKey();
/* Build Form */
$actions = array( 1 => 'spam_service_act_1', 2 => 'spam_service_act_2', 3 => 'spam_service_act_3', 4 => 'spam_service_act_4' );
$actions = array( 1 => 'spam_service_act_1', 5 => 'spam_service_act_5', 2 => 'spam_service_act_2', 3 => 'spam_service_act_3', 4 => 'spam_service_act_4' );
$days = json_decode( \IPS\Settings::i()->spam_service_days, TRUE );
$form = new \IPS\Helpers\Form;
$form->addHeader( 'enhancements__core_SpamMonitoring' );
@@ -215,16 +217,35 @@ class _spam extends \IPS\Dispatcher\Controller
}
}
$form->add( new \IPS\Helpers\Form\YesNo( 'spam_service_enabled', 0, FALSE, array( 'disabled' => $disabled, 'togglesOn' => array( 'spam_service_send_to_ips', 'spam_service_action_0', 'spam_service_action_1', 'spam_service_action_2', 'spam_service_action_3', 'spam_service_action_4' ) ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'spam_service_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 ), NULL, NULL, NULL, 'spam_service_action_1' ) );
$form->add( new \IPS\Helpers\Form\Select( 'spam_service_action_2', \IPS\Settings::i()->spam_service_action_2, FALSE, array( 'disabled' => $disabled, 'options' => $actions ), NULL, NULL, NULL, 'spam_service_action_2' ) );
$form->add( new \IPS\Helpers\Form\Select( 'spam_service_action_3', \IPS\Settings::i()->spam_service_action_3, FALSE, array( 'disabled' => $disabled, 'options' => $actions ), NULL, NULL, NULL, 'spam_service_action_3' ) );
$form->add( new \IPS\Helpers\Form\Select( 'spam_service_action_4', \IPS\Settings::i()->spam_service_action_4, FALSE, array( 'disabled' => $disabled, 'options' => $actions ), NULL, NULL, NULL, 'spam_service_action_4' ) );
$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\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' ) );
$form->add( new \IPS\Helpers\Form\Number( 'spam_service_action_2_num', ( isset( $days[2] ) ? $days[2] : -1 ), FALSE, array( 'unlimited' => -1, 'unlimitedLang' => 'spam_service_action_unlimited_days'), NULL, NULL, \IPS\Member::loggedIn()->language()->addToStack('days'), 'spam_service_action_2_num' ) );
$form->add( new \IPS\Helpers\Form\Select( 'spam_service_action_3', \IPS\Settings::i()->spam_service_action_3, FALSE, array( 'disabled' => $disabled, 'options' => $actions, 'toggles' => array( '5' => array( 'spam_service_action_3_num' ) ) ), NULL, NULL, NULL, 'spam_service_action_3' ) );
$form->add( new \IPS\Helpers\Form\Number( 'spam_service_action_3_num', ( isset( $days[3] ) ? $days[3] : -1 ), FALSE, array( 'unlimited' => -1, 'unlimitedLang' => 'spam_service_action_unlimited_days'), NULL, NULL, \IPS\Member::loggedIn()->language()->addToStack('days'), 'spam_service_action_3_num' ) );
$form->add( new \IPS\Helpers\Form\Select( 'spam_service_action_4', \IPS\Settings::i()->spam_service_action_4, FALSE, array( 'disabled' => $disabled, 'options' => $actions, 'toggles' => array( '5' => array( 'spam_service_action_4_num' ) ) ), NULL, NULL, NULL, 'spam_service_action_4' ) );
$form->add( new \IPS\Helpers\Form\Number( 'spam_service_action_4_num', ( isset( $days[4] ) ? $days[4] : -1 ), FALSE, array( 'unlimited' => -1, 'unlimitedLang' => 'spam_service_action_unlimited_days'), NULL, NULL, \IPS\Member::loggedIn()->language()->addToStack('days'), 'spam_service_action_4_num' ) );
$form->add( new \IPS\Helpers\Form\Select( 'spam_service_action_0', \IPS\Settings::i()->spam_service_action_0, FALSE, array( 'disabled' => $disabled, 'options' => $actions ), NULL, NULL, NULL, 'spam_service_action_0' ) );
if ( $form->values() )
if ( $values = $form->values() )
{
$form->saveAsSettings();
$values['spam_service_days'] = array();
foreach( array( 1, 2, 3, 4 ) as $num )
{
if ( $values['spam_service_action_' . $num ] == 5 )
{
$values['spam_service_days'][ $num ] = $values['spam_service_action_' . $num . '_num' ];
}
unset( $values['spam_service_action_' . $num . '_num' ] );
}
$values['spam_service_days'] = json_encode( $values['spam_service_days'] );
$form->saveAsSettings( $values );
\IPS\Session::i()->log( 'acplog__enhancements_edited', array( 'enhancements__core_SpamMonitoring' => TRUE ) );
}
@@ -156,7 +156,7 @@ class _warnings extends \IPS\Node\Controller
'warn_show_own',
'warnings_acknowledge'
) ) ) );
$form->add( new \IPS\Helpers\Form\Select( 'warn_protected', explode( ',', \IPS\Settings::i()->warn_protected ), FALSE, array( 'options' => \IPS\Member\Group::groups( TRUE, FALSE ), 'parse' => 'normal', 'multiple' => TRUE ), NULL, NULL, NULL, 'warn_protected' ) );
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'warn_protected', explode( ',', \IPS\Settings::i()->warn_protected ), FALSE, array( 'options' => \IPS\Member\Group::groups( TRUE, FALSE ), 'parse' => 'normal', 'multiple' => TRUE ), NULL, NULL, NULL, 'warn_protected' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'warn_show_own', \IPS\Settings::i()->warn_show_own, FALSE, array(), NULL, NULL, NULL, 'warn_show_own' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'warnings_acknowledge', \IPS\Settings::i()->warnings_acknowledge, FALSE, array(), NULL, NULL, NULL, 'warnings_acknowledge' ) );
@@ -212,7 +212,7 @@ class _warnings extends \IPS\Node\Controller
$form->add( new \IPS\Helpers\Form\Number( 'wa_points', ( $current ? $current['wa_points'] : 0 ), TRUE ) );
foreach ( array( 'mq', 'rpa', 'suspend' ) as $k )
{
$form->add( new \IPS\Helpers\Form\Custom( 'wa_' . $k, ( $current ? array( $current[ 'wa_' . $k ], $current[ 'wa_' . $k . '_unit' ] ) : 0 ), FALSE, array(
$form->add( new \IPS\Helpers\Form\Custom( 'wa_' . $k, ( $current ? array( $current[ 'wa_' . $k ], $current[ 'wa_' . $k . '_unit' ] ) : array( NULL, NULL ) ), FALSE, array(
'getHtml' => function( $element )
{
return \IPS\Theme::i()->getTemplate( 'members' )->warningTime( $element->name, $element->value, 'for', 'indefinitely' );
@@ -94,6 +94,10 @@ class _files extends \IPS\Dispatcher\Controller
else
{
$url = \IPS\Http\Url::external( \IPS\Settings::i()->base_url . "applications/core/interface/file/attachment.php" )->setQueryString( 'id', $row['attach_id'] );
if ( $row['attach_security_key'] )
{
$url = $url->setQueryString( 'key', $row['attach_security_key'] );
}
$val = htmlentities( $val, ENT_DISALLOWED, 'UTF-8', FALSE );
return "<a href='{$url}' target='_blank' rel='noreferrer'>{$val}</a>";
}
@@ -303,12 +307,24 @@ class _files extends \IPS\Dispatcher\Controller
{
$settings = json_decode( \IPS\Settings::i()->upload_settings, TRUE );
$configurations = array();
$configurations = array();
$cicDisabled = FALSE;
if( \IPS\CIC )
{
$cicDisabled = array();
}
foreach ( \IPS\Db::i()->select( '*', 'core_file_storage' ) as $row )
{
$handlers = \IPS\File::storageHandlers( $row );
$classname = $handlers[ $row['method'] ];
$configurations[ $row['id'] ] = $classname::displayName( json_decode( $row['configuration'], TRUE ) );
if( $row['method'] == 'FileSystem' AND \IPS\CIC )
{
$cicDisabled[ $row['id'] ] = $row['id'];
}
}
$form = new \IPS\Helpers\Form;
@@ -341,7 +357,14 @@ class _files extends \IPS\Dispatcher\Controller
}
}
$form->add( new \IPS\Helpers\Form\Select( 'filestorage__' . $name, (int) $value, TRUE, array( 'options' => $configurations, 'disabled' => $disabled, 'toggles' => $toggles ) ) );
$handlerDisabled = $cicDisabled;
if( isset( $handlerDisabled[ $value ] ) )
{
unset( $handlerDisabled[ $value ] );
}
$form->add( new \IPS\Helpers\Form\Select( 'filestorage__' . $name, (int) $value, TRUE, array( 'options' => $configurations, 'disabled' => $disabled ?: $handlerDisabled, 'toggles' => $toggles ) ) );
if ( $disabled )
{
@@ -353,6 +376,18 @@ class _files extends \IPS\Dispatcher\Controller
if ( $values = $form->values() )
{
/* Block moves to filesystem on CIC */
foreach ( $values as $k => $v )
{
if ( isset( $settings[ $k ] ) AND !\is_array( $settings[ $k ] ) )
{
if( $settings[ $k ] != $v AND $k != 'filestorage_move' AND \in_array( $v, $cicDisabled ) )
{
\IPS\Output::i()->error( 'file_storage_cic_filesystem', '3C158/7', 403, '' );
}
}
}
if( isset( $values['filestorage_move'] ) AND $values['filestorage_move'] )
{
$rebuild = FALSE;
@@ -376,7 +411,7 @@ class _files extends \IPS\Dispatcher\Controller
if ( $settings[ $k ] != $v and $k != 'filestorage__core_Theme' AND $k != 'filestorage_move' )
{
/* Do we need to move files at all? */
$configurations = \IPS\File::getConfigurations();
$configurations = \IPS\File::getStore();
$exploded = explode( '_', $k );
$classname = "IPS\\{$exploded[2]}\\extensions\\core\\FileStorage\\{$exploded[3]}";
$extension = new $classname;
@@ -434,11 +469,19 @@ class _files extends \IPS\Dispatcher\Controller
$table = new \IPS\Helpers\Table\Db( 'core_file_storage', \IPS\Http\Url::internal( "app=core&module=overview&controller=files&do=settings&tab=configurations" ) );
$table->include = array( 'filestorage_method' );
$table->noSort = array( 'filestorage_method' );
$table->mainColumn = 'filestorage_method';
$table->parsers = array( 'filestorage_method' => function( $val, $row )
{
$handlers = \IPS\File::storageHandlers( $row );
$classname = $handlers[ $row['method'] ];
return $classname::displayName( json_decode( $row['configuration'], TRUE ) );
$title = $classname::displayName( json_decode( $row['configuration'], TRUE ) );
if( \IPS\CIC AND $row['method'] == 'FileSystem' )
{
return \IPS\Theme::i()->getTemplate( 'dashboard' )->filesystemNotCic( $title );
}
return $title;
} );
$table->rootButtons = array( 'add' => array(
@@ -52,6 +52,7 @@ class _onboard extends \IPS\Dispatcher\Controller
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('onboard_pagetitle');
\IPS\Output::i()->showTitle = FALSE;
\IPS\Output::i()->bypassCsrfKeyCheck = TRUE;
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'dashboard' )->onboardWelcome();
}
@@ -97,6 +98,9 @@ class _onboard extends \IPS\Dispatcher\Controller
$form->add( new \IPS\Helpers\Form\Address( 'site_address', \IPS\GeoLocation::buildFromJson( \IPS\Settings::i()->site_address ), FALSE ) );
$form->add( new \IPS\Helpers\Form\Stack( 'site_social_profiles', \IPS\Settings::i()->site_social_profiles ? json_decode( \IPS\Settings::i()->site_social_profiles, true ) : array(), FALSE, array( 'stackFieldType' => '\IPS\core\Form\SocialProfiles', 'maxItems' => 50, 'key' => array( 'placeholder' => 'http://example.com', 'size' => 20 ) ) ) );
$form->add( new \IPS\Helpers\Form\Email( 'email_out', \IPS\Settings::i()->email_out, TRUE, array(), NULL, NULL, NULL, 'email_out' ) );
$form->add( new \IPS\Helpers\Form\Email( 'email_in', \IPS\Settings::i()->email_in, TRUE ) );
$form->addTab( 'onboard_tab_appearance' );
/* If we do not have an email logo and none of our themes have a logo, show the logo field */
@@ -115,6 +115,7 @@ class _advertisements extends \IPS\Dispatcher\Controller
$table = new \IPS\Helpers\Table\Db( 'core_advertisements', $url, $where );
$table->langPrefix = 'ads_';
$table->rowClasses = array( 'ad_html' => array( 'ipsTable_wrap' ) );
/* Columns we need */
$table->include = array( 'word_custom', 'ad_html', 'ad_impressions' );
@@ -368,7 +369,14 @@ class _advertisements extends \IPS\Dispatcher\Controller
\IPS\Session::i()->log( 'acplog_ad_disabled', array() );
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=promotion&controller=advertisements' )->setQueryString( 'filter', \IPS\Request::i()->filter ), \IPS\Request::i()->status ? 'ad_toggled_visible' : 'ad_toggled_notvisible' );
if( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->json( 'OK' );
}
else
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=promotion&controller=advertisements' )->setQueryString( 'filter', \IPS\Request::i()->filter ), \IPS\Request::i()->status ? 'ad_toggled_visible' : 'ad_toggled_notvisible' );
}
}
/**
@@ -508,7 +516,7 @@ class _advertisements extends \IPS\Dispatcher\Controller
$form->add( new \IPS\Helpers\Form\Stack( 'ad_location_custom', $customLocations, FALSE, array(), NULL, NULL, NULL, 'ad_location_custom' ) );
/* Generic fields available for both html and image ads */
$form->add( new \IPS\Helpers\Form\Select( 'ad_exempt', ( $record->id ) ? ( ( $record->exempt == '*' ) ? '*' : json_decode( $record->exempt, TRUE ) ) : '*', FALSE, array( 'options' => \IPS\Member\Group::groups(), 'parse' => 'normal', 'multiple' => TRUE, 'unlimited' => '*', 'unlimitedLang' => 'everyone' ), NULL, NULL, NULL, 'ad_exempt' ) );
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'ad_exempt', ( $record->id ) ? ( ( $record->exempt == '*' ) ? '*' : json_decode( $record->exempt, TRUE ) ) : '*', FALSE, array( 'options' => \IPS\Member\Group::groups(), 'parse' => 'normal', 'multiple' => TRUE, 'unlimited' => '*', 'unlimitedLang' => 'everyone', 'impliedUnlimited' => TRUE ), NULL, NULL, NULL, 'ad_exempt' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'ad_nocontent_page_output', ( $record->id ) ? $record->nocontent_page_output : FALSE ) );
}
else
@@ -73,12 +73,18 @@ class _seo extends \IPS\Dispatcher\Controller
/* Build tab list */
$tabs = array();
if ( !\IPS\CIC )
if ( !\IPS\CIC AND \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'promotion', 'seo_furls' ) )
{
$tabs['urls'] = 'seo_tab_furls';
}
$tabs['sitemap'] = 'seo_tab_sitemap';
$tabs['metatags'] = 'seo_tab_metatags';
if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'promotion', 'seo_sitemap' ) )
{
$tabs['sitemap'] = 'seo_tab_sitemap';
}
if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'promotion', 'seo_meta' ) )
{
$tabs['metatags'] = 'seo_tab_metatags';
}
/* Display */
if ( $activeTabContents )
@@ -141,11 +141,12 @@ class _advanced extends \IPS\Dispatcher\Controller
$toggles = array();
$disabled = array();
$storeConfigurationFields = array();
$options = array(
'FileSystem' => 'datastore_method_FileSystem',
'Database' => 'datastore_method_Database',
'Redis' => 'datastore_method_Redis',
);
$options = [];
foreach( \IPS\Data\Store::availableMethods() AS $key => $class )
{
$options[ $key ] = 'datastore_method_' . $key;
}
$existingConfiguration = json_decode( \IPS\STORE_CONFIG, TRUE );
foreach ( $options as $k => $v )
{
@@ -190,17 +191,10 @@ class _advanced extends \IPS\Dispatcher\Controller
$toggles = array( 'Redis' => array( 'redis_enabled' ) );
$disabled = array();
$cacheConfigurationFields = array();
$options = array(
'None' => 'datastore_method_None',
'Apc' => 'datastore_method_Apc',
'Memcache' => 'datastore_method_Memcache',
'Redis' => 'datastore_method_Redis',
'Wincache' => 'datastore_method_Wincache',
'Xcache' => 'datastore_method_Xcache',
);
if ( \IPS\TEST_CACHING )
$options = [];
foreach( \IPS\Data\Cache::availableMethods() AS $key => $class )
{
$options['Test'] = 'datastore_method_Test';
$options[ $key ] = 'datastore_method_' . $key;
}
$cacheConfig = \IPS\CACHE_CONFIG;
@@ -211,20 +205,10 @@ class _advanced extends \IPS\Dispatcher\Controller
}
$existingConfiguration = json_decode( $cacheConfig, TRUE );
/* Remove deprecated options if they're not in use @todo 4.6 */
$deprecatedCacheMethods = array( 'Apc', 'Memcache', 'Wincache', 'Xcache' );
foreach( $deprecatedCacheMethods as $cache )
{
if( $cache !== \IPS\CACHE_METHOD )
{
unset( $options[ $cache ] );
}
}
foreach ( $options as $k => $v )
{
$class = 'IPS\Data\Cache\\' . $k;
$class = \IPS\Data\Cache::availableMethods()[ $k ];
if ( !$class::supported() )
{
$disabled[] = $k;
@@ -243,12 +227,6 @@ class _advanced extends \IPS\Dispatcher\Controller
$toggles[ $k ][] = $input->htmlId;
$cacheConfigurationFields[ $k ][ $inputKey ] = $input->name;
}
/* Deprecated Warnings */
if( \in_array( $k, $deprecatedCacheMethods ) )
{
\IPS\Member::loggedIn()->language()->words["datastore_method_{$k}_desc"] = \IPS\Member::loggedIn()->language()->addToStack( 'datastore_method_deprecated', FALSE, array( 'sprintf' => array( $k ) ) );
}
}
}
$form->add( new \IPS\Helpers\Form\Radio( 'cache_method', \IPS\CACHE_METHOD, TRUE, array(
@@ -497,6 +475,14 @@ class _advanced extends \IPS\Dispatcher\Controller
)
) ) );
$form->add( new \IPS\Helpers\Form\Text( 'csp_header', \IPS\Settings::i()->csp_header, FALSE, array( 'placeholder' => "default-src *; frame-ancestors 'self' *.example.com" ), NULL, NULL, NULL, 'csp_header' ) );
$form->add( new \IPS\Helpers\Form\Radio( 'referrer_policy_header', \IPS\Settings::i()->referrer_policy_header, FALSE, array(
'options' => array(
'0' => 'referrerpolicy_disabled',
'1' => 'referrerpolicy_acp_only',
'2' => 'referrerpolicy_enabled',
)
) ) );
$form->addHeader('performance_settings');
$form->add( new \IPS\Helpers\Form\Interval( 'widget_cache_ttl', ( isset( \IPS\Settings::i()->widget_cache_ttl ) ) ? \IPS\Settings::i()->widget_cache_ttl : 60, FALSE, array( 'valueAs' => \IPS\Helpers\Form\Interval::SECONDS, 'min' => 60 ), NULL, \IPS\Member::loggedIn()->language()->addToStack('for'), NULL ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'lazy_load_enabled', \IPS\Settings::i()->lazy_load_enabled, FALSE ) );
@@ -761,11 +747,13 @@ class _advanced extends \IPS\Dispatcher\Controller
$output = \IPS\Member::loggedIn()->language()->addToStack( $output, FALSE );
}
\IPS\Output::i()->bypassCsrfKeyCheck = true;
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'advancedsettings' )->taskResult( TRUE, $output, $task->id );
}
}
catch ( \IPS\Task\Exception $e )
{
\IPS\Output::i()->bypassCsrfKeyCheck = true;
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'advancedsettings' )->taskResult( FALSE, \IPS\Member::loggedIn()->language()->addToStack( $e->getMessage(), FALSE ), $task->id );
}
catch ( \RuntimeException $e )
@@ -84,11 +84,12 @@ class _contactus extends \IPS\Dispatcher\Controller
$disabled = array();
$formFields = array();
$form->add( new \IPS\Helpers\Form\Select( 'contact_access', ( \IPS\Settings::i()->contact_access == '*' ) ? '*' : explode( ',', \IPS\Settings::i()->contact_access ), FALSE, array(
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'contact_access', ( \IPS\Settings::i()->contact_access == '*' ) ? '*' : explode( ',', \IPS\Settings::i()->contact_access ), FALSE, array(
'options' => array_combine( array_keys( \IPS\Member\Group::groups() ), array_map( function( $_group ) { return (string) $_group; }, \IPS\Member\Group::groups() ) ),
'multiple' => true,
'unlimited' => '*',
'unlimitedLang' => 'everyone'
'unlimitedLang' => 'everyone',
'impliedUnlimited' => TRUE
), NULL, NULL, NULL, 'contact_access' ) );
/* Get extensions */
@@ -102,7 +102,7 @@ class _email extends \IPS\Dispatcher\Controller
$settingsForm->add( new \IPS\Helpers\Form\Select( 'smtp_protocol', \IPS\Settings::i()->smtp_protocol, FALSE, array( 'options' => $options ), NULL, NULL, NULL, 'smtp_protocol' ) );
$settingsForm->add( new \IPS\Helpers\Form\Number( 'smtp_port', \IPS\Settings::i()->smtp_port, FALSE, array(), NULL, NULL, NULL, 'smtp_port' ) );
$settingsForm->add( new \IPS\Helpers\Form\Text( 'smtp_user', \IPS\Settings::i()->smtp_user, FALSE, array(), NULL, NULL, NULL, 'smtp_user' ) );
$settingsForm->add( new \IPS\Helpers\Form\Password( 'smtp_pass', \IPS\Settings::i()->smtp_pass, FALSE, array(), NULL, NULL, NULL, 'smtp_pass' ) );
$settingsForm->add( new \IPS\Helpers\Form\Password( 'smtp_pass', \IPS\Settings::i()->smtp_pass, FALSE, array( 'enforceMaxLimit' => FALSE ), NULL, NULL, NULL, 'smtp_pass' ) );
if ( !\IPS\CIC )
{
$settingsForm->add( new \IPS\Helpers\Form\Text( 'php_mail_extra', \IPS\Settings::i()->php_mail_extra, FALSE, array(), NULL, NULL, NULL, 'php_mail_extra' ) );
@@ -155,6 +155,7 @@ class _email extends \IPS\Dispatcher\Controller
/* Sent successfully, remove notification */
\IPS\core\AdminNotification::remove( 'core', 'ConfigurationError', 'failedMail' );
\IPS\Db::i()->update( 'core_mail_error_logs', array( 'mlog_notification_sent' => TRUE ) );
}
catch ( \Exception $e )
{
@@ -189,6 +190,7 @@ class _email extends \IPS\Dispatcher\Controller
/* Sent successfully, remove notification */
\IPS\core\AdminNotification::remove( 'core', 'ConfigurationError', 'failedMail' );
\IPS\Db::i()->update( 'core_mail_error_logs', array( 'mlog_notification_sent' => TRUE ) );
$messageHtml = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->message( 'email_test_okay', 'success' );
}
@@ -363,7 +365,7 @@ class _email extends \IPS\Dispatcher\Controller
unset( $emailData['headers']['Content-Type'] );
unset( $emailData['headers']['Content-Transfer-Encoding'] );
$email = \IPS\Email::buildFromContent( $log['mlog_subject'], $emailData['body']['html'], $emailData['body']['plain'], isset( $emailData['type'] ) ? $emailData['type'] : NULL );
$email = \IPS\Email::buildFromContent( $log['mlog_subject'], $emailData['body']['html'], $emailData['body']['plain'], isset( $emailData['type'] ) ? $emailData['type'] : \IPS\Email::TYPE_TRANSACTIONAL );
$email->_send(
array_map( 'trim', explode( ',', $log['mlog_to'] ) ),
isset( $emailData['headers']['Cc'] ) ? array_map( 'trim', explode( ',', $emailData['headers']['Cc'] ) ) : array(),
@@ -381,9 +383,10 @@ class _email extends \IPS\Dispatcher\Controller
\IPS\Db::i()->delete( 'core_mail_error_logs', array( 'mlog_id=?', $id ) );
/* Remove notification since this was re-sent with success and it is below the recent threshold */
if( \IPS\Email::countFailedMail( \IPS\DateTime::create()->sub( new \DateInterval( 'P3D' ), FALSE ), TRUE ) <= 3 )
if( \IPS\Email::countFailedMail( \IPS\DateTime::create()->sub( new \DateInterval( 'P3D' ) ), FALSE, TRUE ) <= 3 )
{
\IPS\core\AdminNotification::remove( 'core', 'ConfigurationError', 'failedMail' );
\IPS\Db::i()->update( 'core_mail_error_logs', array( 'mlog_notification_sent' => TRUE ), array( 'mlog_id=?', $id ) );
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=settings&controller=email&do=errorLog" ), 'emailerror_resent' );
@@ -56,6 +56,7 @@ class _general extends \IPS\Dispatcher\Controller
$form->add( new \IPS\Helpers\Form\Stack( 'site_social_profiles', \IPS\Settings::i()->site_social_profiles ? json_decode( \IPS\Settings::i()->site_social_profiles, true ) : array(), FALSE, array( 'stackFieldType' => '\IPS\core\Form\SocialProfiles', 'maxItems' => 50, 'key' => array( 'placeholder' => 'http://example.com', 'size' => 20 ) ) ) );
$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\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',
@@ -75,9 +76,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 );
@@ -423,6 +423,12 @@ class _login extends \IPS\Node\Controller
{
$values['allowed_reg_email'] = implode( ',', $values['allowed_reg_email'] );
}
if ( isset( $values['coppa_address'] ) AND ( $values['coppa_address'] instanceof \IPS\GeoLocation ) )
{
$values['coppa_address'] = json_encode( $values['coppa_address'] );
}
$form->saveAsSettings( $values );
\IPS\Session::i()->log( 'acplogs__registration_settings' );
@@ -197,11 +197,12 @@ class _mfa extends \IPS\Dispatcher\Controller
$form->addHeader('mfa_header_setup');
$groups = array_combine( array_keys( \IPS\Member\Group::groups() ), array_map( function( $_group ) { return (string) $_group; }, \IPS\Member\Group::groups() ) );
$form->add( new \IPS\Helpers\Form\Select( 'mfa_required_groups', \IPS\Settings::i()->mfa_required_groups == '*' ? '*' : explode( ',', \IPS\Settings::i()->mfa_required_groups ), FALSE, array(
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'mfa_required_groups', \IPS\Settings::i()->mfa_required_groups == '*' ? '*' : explode( ',', \IPS\Settings::i()->mfa_required_groups ), FALSE, array(
'multiple' => TRUE,
'options' => $groups,
'unlimited' => '*',
'unlimitedLang' => 'everyone',
'impliedUnlimited' => TRUE
) ) );
$form->add( new \IPS\Helpers\Form\Radio( 'mfa_required_prompt', \IPS\Settings::i()->mfa_required_prompt, FALSE, array(
'options' => array(
@@ -121,7 +121,7 @@ class _posting extends \IPS\Dispatcher\Controller
$form->addHeader('posting_images');
$form->add( new \IPS\Helpers\Form\WidthHeight( 'attachment_resample_size', \IPS\Settings::i()->attachment_resample_size ? explode( 'x', \IPS\Settings::i()->attachment_resample_size ) : array( 0, 0 ), FALSE, array( 'resizableDiv' => FALSE, 'unlimited' => array( 0, 0 ) ), NULL, NULL, NULL, 'attachment_resample_size' ) );
$current = ( isset( \IPS\Settings::i()->attachment_image_size ) ) ? explode( 'x', \IPS\Settings::i()->attachment_image_size ) : array( 1000, 750 );
$form->add( new \IPS\Helpers\Form\WidthHeight( 'attachment_image_size', $current, FALSE, array( 'resizableDiv' => FALSE ), NULL, NULL, NULL, 'attachment_image_size' ) );
$form->add( new \IPS\Helpers\Form\WidthHeight( 'attachment_image_size', $current, FALSE, array( 'resizableDiv' => FALSE, 'unlimited' => array( 0, 0 ) ), NULL, NULL, NULL, 'attachment_image_size' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'editor_embeds', \IPS\Settings::i()->editor_embeds, FALSE, array( 'togglesOn' => array( 'max_video_width', 'max_internalembed_width' ) ) ) );
$form->add( new \IPS\Helpers\Form\Number( 'max_video_width', \IPS\Settings::i()->max_video_width, FALSE, array( 'unlimited' => 0 ), NULL, NULL, 'px', 'max_video_width' ) );
$form->add( new \IPS\Helpers\Form\Number( 'max_internalembed_width', \IPS\Settings::i()->max_internalembed_width, FALSE, array( 'unlimited' => 0 ), NULL, NULL, 'px', 'max_internalembed_width' ) );
@@ -130,6 +130,10 @@ class _posting extends \IPS\Dispatcher\Controller
$form->add( new \IPS\Helpers\Form\YesNo( 'allow_remote_images', \IPS\Settings::i()->allow_remote_images, FALSE, array( 'togglesOn' => array( 'allow_only_https_remote_images' ) ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'allow_only_https_remote_images', \IPS\Settings::i()->allow_only_https_remote_images, FALSE, array(), NULL, NULL, NULL, 'allow_only_https_remote_images' ) );
$form->addHeader('post_settings_embed');
$form->add( new \IPS\Helpers\Form\Text( 'fb_ig_oembed_appid', \IPS\Settings::i()->fb_ig_oembed_appid, FALSE ) );
$form->add( new \IPS\Helpers\Form\Text( 'fb_ig_oembed_appsecret', \IPS\Settings::i()->fb_ig_oembed_appsecret, FALSE ) );
$form->addHeader('posting_content');
if ( \IPS\Login::registrationType() != 'disabled' )
{
@@ -193,8 +197,8 @@ class _posting extends \IPS\Dispatcher\Controller
$clearCss = ( $values['max_video_width'] !== \IPS\Settings::i()->max_video_width );
$values['attachment_resample_size'] = implode( 'x', $values['attachment_resample_size'] );
$values['attachment_image_size'] = implode( 'x', $values['attachment_image_size'] );
$values['attachment_resample_size'] = implode( 'x', $values['attachment_resample_size'] );
$values['attachment_image_size'] = implode( 'x', $values['attachment_image_size'] );
$values['attach_allowed_extensions'] = \is_array( $values['attach_allowed_extensions'] ) ? implode( ',', array_unique( array_map( function( $val )
{
return ltrim( $val, '.' );
@@ -539,7 +543,8 @@ class _posting extends \IPS\Dispatcher\Controller
'type' => $values['profanity_type'],
'swop' => $values['profanity_swop'],
'm_exact' => $values['profanity_m_exact'],
'action' => $values['profanity_action']
'action' => $values['profanity_action'],
'min_posts' => $values['profanity_min_posts']
);
\IPS\core\Profanity::createFromForm( $save, $current );
@@ -631,8 +636,7 @@ class _posting extends \IPS\Dispatcher\Controller
$data = \intval( $data );
/* Open XML file */
$xml = new \IPS\Xml\XMLReader;
$xml->open( \IPS\Request::i()->file );
$xml = \IPS\Xml\XMLReader::safeOpen( \IPS\Request::i()->file );
$xml->read(); //badwordexport
$xml->read(); //badwordexport
$xml->read(); //badwordgroup
@@ -781,4 +785,35 @@ class _posting extends \IPS\Dispatcher\Controller
/* And redirect */
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=settings&controller=posting" ), 'thumbnails_rebuilt' );
}
/**
* Rebuild URL refs
*
* @return void
*/
public function rebuildUrlRels()
{
\IPS\Session::i()->csrfCheck();
/* Remove any existing rebuilds */
\IPS\Db::i()->delete( 'core_queue', \IPS\Db::i()->in( '`key`', array( 'RebuildUrlRels' ) ) );
/* Unset task datastore */
unset( \IPS\Data\Store::i()->currentUrlRefRebuild );
foreach ( \IPS\Content::routedClasses( FALSE, TRUE ) as $class )
{
if( isset( $class::$databaseColumnMap['content'] ) )
{
try
{
\IPS\Task::queue( 'core', 'RebuildUrlRels', array( 'class' => $class ), 4 );
}
catch( \OutOfRangeException $ex ) { }
}
}
/* And redirect */
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=settings&controller=posting" ), 'refurls_rebuilt' );
}
}
@@ -67,7 +67,7 @@ class _securityquestions extends \IPS\Node\Controller
* @param string $lastUsedTab The tab last used in the form
* @return void
*/
protected function _afterSave( \IPS\Node\Model $old = NULL, \IPS\Node\Model $new, $lastUsedTab = FALSE )
protected function _afterSave( ?\IPS\Node\Model $old, \IPS\Node\Model $new, $lastUsedTab = FALSE )
{
if( \IPS\Request::i()->isAjax() )
{
+58 -23
View File
@@ -259,7 +259,17 @@ class _admin extends \IPS\Dispatcher\Controller
'row_updated' => time()
);
\IPS\Db::i()->insert( 'core_admin_permission_rows', $current );
\IPS\Session::i()->log( 'acplog__acpr_created', array( ( $values['acprestrictions_type'] == 'group' ? \IPS\Member\Group::load( $values['acprestrictions_group'] )->name : $values['acprestrictions_member']->name ) => FALSE ) );
if( $values['acprestrictions_type'] == 'group' )
{
$logValue = array( 'core_group_' . $values['acprestrictions_group'] => TRUE );
}
else
{
$logValue = array( $values['acprestrictions_member']->name => FALSE );
}
\IPS\Session::i()->log( 'acplog__acpr_created', $logValue );
}
else
{
@@ -316,24 +326,37 @@ class _admin extends \IPS\Dispatcher\Controller
$restrictions = array();
foreach ( \IPS\Application::applications() as $app )
{
$restrictions['applications'][ $app->directory ] = $app->id;
$_restrictions = array();
$file = \IPS\ROOT_PATH . "/applications/{$app->directory}/data/acprestrictions.json";
if ( file_exists( $file ) )
{
$_restrictions = json_decode( file_get_contents( $file ), TRUE );
}
foreach ( $app->modules( 'admin' ) as $module )
{
if ( !$module->protected )
if ( !\IPS\CIC or !\in_array( "{$app->directory}", \IPS\Member::$cicBlockedAcpRestrictions ) )
{
$restrictions['applications'][ $app->directory ] = $app->id;
$_restrictions = array();
$file = $app->getApplicationPath() . '/data/acprestrictions.json';
if ( file_exists( $file ) )
{
$restrictions['modules'][ $app->id ][ $module->key ] = $module->id;
if ( isset( $_restrictions[ $module->key ] ) )
$_restrictions = json_decode( file_get_contents( $file ), TRUE );
}
foreach ( $app->modules( 'admin' ) as $module )
{
if ( !$module->protected and ( !\IPS\CIC or !\in_array( "{$app->directory}.{$module->key}", \IPS\Member::$cicBlockedAcpRestrictions ) ) )
{
$restrictions['items'][ $module->id ] = $_restrictions[ $module->key ];
$restrictions['modules'][ $app->id ][ $module->key ] = $module->id;
if ( isset( $_restrictions[ $module->key ] ) )
{
foreach ( $_restrictions[ $module->key ] as $group => $items )
{
foreach ( $items as $restrictionKey => $langKey )
{
if ( !\IPS\CIC or !\in_array( "{$app->directory}.{$module->key}.{$restrictionKey}", \IPS\Member::$cicBlockedAcpRestrictions ) )
{
$restrictions['items'][ $module->id ][ $group ][ $restrictionKey ] = $langKey;
}
}
}
}
}
}
}
@@ -369,7 +392,16 @@ class _admin extends \IPS\Dispatcher\Controller
unset( \IPS\Data\Store::i()->administrators );
/* Log */
\IPS\Session::i()->log( 'acplog__acpr_edited', array( ( $current['row_id_type'] == 'group' ? \IPS\Member\Group::load( $current['row_id'] )->name : \IPS\Member::load( $current['row_id'] )->name ) => FALSE ) );
if( $current['row_id_type'] == 'group' )
{
$logValue = array( 'core_group_' . $current['row_id'] => TRUE );
}
else
{
$logValue = array( \IPS\Member::load( $current['row_id'] )->name => FALSE );
}
\IPS\Session::i()->log( 'acplog__acpr_edited', $logValue );
/* Boink */
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=staff&controller=admin' ), 'saved' );
@@ -400,12 +432,14 @@ class _admin extends \IPS\Dispatcher\Controller
{
try
{
$name = \IPS\Member\Group::load( $current['row_id'] )->name;
$name = 'core_group_' . $current['row_id'];
}
catch( \OutOfRangeException $e )
{
$name = \IPS\Member::loggedIn()->language()->get('deleted_group');
$name = 'deleted_group';
}
$logValue = array( $name => TRUE );
}
else
{
@@ -413,15 +447,16 @@ class _admin extends \IPS\Dispatcher\Controller
if( $member->member_id )
{
$name = $member->name;
$logValue = array( $member->name => FALSE );
}
else
{
$name = \IPS\Member::loggedIn()->language()->get('deleted_member');
$logValue = array( 'deleted_member' => TRUE );
}
}
\IPS\Session::i()->log( 'acplog__acpr_deleted', array( $name => FALSE ) );
\IPS\Session::i()->log( 'acplog__acpr_deleted', $logValue );
\IPS\Db::i()->delete( 'core_admin_permission_rows', array( 'row_id=? AND row_id_type=?', \intval( \IPS\Request::i()->id ), \IPS\Request::i()->type ) );
unset ( \IPS\Data\Store::i()->administrators );
@@ -224,8 +224,17 @@ class _moderators extends \IPS\Dispatcher\Controller
{
$ext->onChange( $current, $values );
}
\IPS\Session::i()->log( 'acplog__moderator_created', array( ( $values['moderators_type'] == 'g' ? \IPS\Member::loggedIn()->language()->get( "core_group_{$values['moderators_group']}" ) : $values['moderators_member']->name ) => FALSE ) );
if( $values['moderators_type'] == 'g' )
{
$logValue = array( 'core_group_' . $values['moderators_group'] => TRUE );
}
else
{
$logValue = array( $values['moderators_member']->name => FALSE );
}
\IPS\Session::i()->log( 'acplog__moderator_created', $logValue );
unset (\IPS\Data\Store::i()->moderators);
}
@@ -481,8 +490,18 @@ class _moderators extends \IPS\Dispatcher\Controller
$ext->onChange( $current, $changed );
}
}
\IPS\Session::i()->log( 'acplog__moderator_edited', array( ( $current['type'] == 'g' ? \IPS\Member::loggedIn()->language()->get( "core_group_{$current['id']}" ) : \IPS\Member::load( $current['id'] )->name ) => FALSE ) );
if( $current['type'] == 'g' )
{
$logValue = array( 'core_group_' . $current['id'] => TRUE );
}
else
{
$logValue = array( \IPS\Member::load( $current['id'] )->name => FALSE );
}
\IPS\Session::i()->log( 'acplog__moderator_edited', $logValue );
$currentPermissions = $values;
unset( \IPS\Data\Store::i()->moderators );
@@ -536,7 +555,35 @@ class _moderators extends \IPS\Dispatcher\Controller
unset (\IPS\Data\Store::i()->moderators);
/* Log and redirect */
\IPS\Session::i()->log( 'acplog__moderator_deleted', array( ( $current['type'] == 'g' ? \IPS\Member::loggedIn()->language()->get( "core_group_{$current['id']}" ) : \IPS\Member::load( $current['id'] )->name ) => FALSE ) );
if( $current['type'] == 'g' )
{
try
{
$name = 'core_group_' . $current['id'];
}
catch( \OutOfRangeException $e )
{
$name = 'deleted_group';
}
$logValue = array( $name => TRUE );
}
else
{
$member = \IPS\Member::load( $current['id'] );
if( $member->member_id )
{
$logValue = array( $member->name => FALSE );
}
else
{
$logValue = array( 'deleted_member' => TRUE );
}
}
\IPS\Session::i()->log( 'acplog__moderator_deleted', $logValue );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=staff&controller=moderators' ) );
}
@@ -0,0 +1,71 @@
<?php
/**
* @brief badges
* @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 Mar 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;
}
/**
* badges
*/
class _badges extends \IPS\Dispatcher\Controller
{
/**
* @brief Allow MySQL RW separation for efficiency
*/
public static $allowRWSeparation = TRUE;
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'overview_manage' );
parent::execute();
}
/**
* Points 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' );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('stats_badges_title');
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart' );
$chart->enableHourly = FALSE;
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack('stats_member_badges'), 'number', 'COUNT(*)', TRUE );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('stats_badges_title');
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart' );
$chart->showIntervals = TRUE;
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_stats_badges');
\IPS\Output::i()->output = (string) $chart;
}
}
@@ -26,6 +26,11 @@ class _deviceusage extends \IPS\Dispatcher\Controller
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* @brief Allow MySQL RW separation for efficiency
*/
public static $allowRWSeparation = TRUE;
/**
* Execute
@@ -26,6 +26,11 @@ class _membersactivity extends \IPS\Dispatcher\Controller
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* @brief Allow MySQL RW separation for efficiency
*/
public static $allowRWSeparation = TRUE;
/**
* Execute
@@ -26,6 +26,11 @@ class _membervisits extends \IPS\Dispatcher\Controller
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* @brief Allow MySQL RW separation for efficiency
*/
public static $allowRWSeparation = TRUE;
/**
* Execute
@@ -26,6 +26,11 @@ class _onlineusers extends \IPS\Dispatcher\Controller
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* @brief Allow MySQL RW separation for efficiency
*/
public static $allowRWSeparation = TRUE;
/**
* Execute
@@ -27,6 +27,11 @@ class _overview extends \IPS\Dispatcher\Controller
*/
public static $csrfProtected = TRUE;
/**
* @brief Allow MySQL RW separation for efficiency
*/
public static $allowRWSeparation = TRUE;
/**
* @brief Date range to restrict to, or NULL for no restriction
*/
@@ -0,0 +1,71 @@
<?php
/**
* @brief points
* @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 Mar 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;
}
/**
* points
*/
class _points extends \IPS\Dispatcher\Controller
{
/**
* @brief Allow MySQL RW separation for efficiency
*/
public static $allowRWSeparation = TRUE;
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'overview_manage' );
parent::execute();
}
/**
* Points earned activity chart
*
* @return void
*/
protected function manage()
{
$chart = new \IPS\Helpers\Chart\Database( \IPS\Http\Url::internal( 'app=core&module=stats&controller=points' ), 'core_points_log', 'datetime', '', array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'colors' => array( '#10967e', '#ea7963', '#de6470', '#6b9dde' ),
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4
), 'AreaChart', 'daily' );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('stats_points_title');
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart' );
$chart->enableHourly = FALSE;
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack('stats_member_points'), 'number', 'SUM(points)', TRUE );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('stats_points_title');
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart' );
$chart->showIntervals = TRUE;
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_stats_points');
\IPS\Output::i()->output = (string) $chart;
}
}
@@ -26,6 +26,11 @@ class _registrationstats extends \IPS\Dispatcher\Controller
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* @brief Allow MySQL RW separation for efficiency
*/
public static $allowRWSeparation = TRUE;
/**
* Manage Members
@@ -40,13 +40,13 @@ class _hookedFiles extends \IPS\Dispatcher\Controller
}
/**
* ...
* Show all third party hooks
*
* @return void
*/
protected function manage()
{
$table = new \IPS\Helpers\Table\Db( 'core_hooks', \IPS\Http\Url::internal( 'app=core&module=support&controller=hookedFiles' ) );
$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->langPrefix = 'hooks_';
$table->quickSearch = array( 'class', 'hook_class' );
$table->include = array( 'hooks_app', 'hooks_plugin', 'hooks_type', 'hooks_class', 'hooks_file' );
@@ -104,7 +104,7 @@ class _hookedFiles extends \IPS\Dispatcher\Controller
);
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'hooked_classes' );
\IPS\Output::i()->output = $table;
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'support' )->hookedClasses( $table );
}
}
@@ -0,0 +1,51 @@
<?php
/**
* @brief Display phpinfo
* @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 4 December 2020
*/
namespace IPS\core\modules\admin\support;
/* 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;
}
/**
* Display phpinfo
*/
class _phpinfo extends \IPS\Dispatcher\Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'get_support' );
parent::execute();
}
/**
* Support Wizard
*
* @return void
*/
protected function manage()
{
phpinfo();
exit;
}
}
@@ -88,12 +88,14 @@ class _redis extends \IPS\Dispatcher\Controller
if ( \IPS\CACHE_METHOD == 'Redis' and \IPS\REDIS_ENABLED )
{
$datasource[] = array( 'type' => 'redis_sessions', 'count' => \count( \IPS\Redis::i()->debugGetKeys( 'session_id_*', TRUE ) ), 'enabled' => true );
$datasource[] = array( 'type' => 'redis_topic_views', 'count' => \IPS\Redis::i()->zSize('topic_views'), 'enabled' => true );
$datasource[] = array( 'type' => 'redis_topic_views', 'count' => \IPS\Redis::i()->zCard('topic_views'), 'enabled' => true );
$datasource[] = array( 'type' => 'redis_advert_impressions', 'count' => \IPS\Redis::i()->zCard('advert_impressions'), 'enabled' => true );
}
else
{
$datasource[] = array( 'type' => 'redis_sessions' , 'count' => NULL, 'enabled' => false );
$datasource[] = array( 'type' => 'redis_topic_views', 'count' => NULL, 'enabled' => false );
$datasource[] = array( 'type' => 'redis_advert_impressions', 'count' => NULL, 'enabled' => false );
}
}
@@ -129,6 +131,6 @@ class _redis extends \IPS\Dispatcher\Controller
);
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('redis_info');
\IPS\Output::i()->output = \IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'support' )->redis( $info, $table );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'support' )->redis( $info, $table );
}
}
@@ -82,7 +82,7 @@ class _sql extends \IPS\Dispatcher\Controller
'view' => array(
'icon' => 'search',
'title' => 'view',
'link' => \IPS\Http\Url::internal( 'app=core&module=support&controller=sql&do=query' )->setQueryString( 'q', "SELECT * FROM {$row['name']}" )->csrf()
'link' => \IPS\Http\Url::internal( 'app=core&module=support&controller=sql&do=query&action=view' )->setQueryString( 'table', $row['name'] )
),
);
};
@@ -103,22 +103,31 @@ class _sql extends \IPS\Dispatcher\Controller
*/
protected function query()
{
\IPS\Session::i()->csrfCheck();
/* Select from table */
$query = NULL;
if ( isset( \IPS\Request::i()->q ) )
$table = NULL;
if ( isset( \IPS\Request::i()->table ) AND isset( \IPS\Request::i()->action ) )
{
$query = urldecode( \IPS\Request::i()->q );
$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 */
@@ -159,13 +168,21 @@ class _sql extends \IPS\Dispatcher\Controller
{
$page = isset( \IPS\Request::i()->page ) ? \intval( \IPS\Request::i()->page ) : 1;
if( $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(
\IPS\Http\Url::internal( 'app=core&module=support&controller=sql&do=query' )->setQueryString( 'q', $query )->csrf(),
$paginationUrl,
ceil( $result->num_rows / static::SELECT_LIMIT ),
$page,
static::SELECT_LIMIT
File diff suppressed because it is too large. Load diff
@@ -104,7 +104,7 @@ class _utf8mb4 extends \IPS\Dispatcher\Controller
return $wizardData;
}
$baseUrl = \IPS\Http\Url::internal( 'app=core&module=support&controller=utf8mb4' );
$baseUrl = \IPS\Http\Url::internal( 'app=core&module=support&controller=utf8mb4' )->csrf();
return new \IPS\Helpers\MultipleRedirect( $baseUrl,
function( $mrData )
@@ -197,7 +197,7 @@ class _utf8mb4 extends \IPS\Dispatcher\Controller
$length += $tableDefinition['columns'][ $column ]['length'];
}
if( $tableDefinition['columns'][ $column ]['collation'] !== 'utf8mb4_unicode_ci' )
if( isset( $tableDefinition['columns'][ $column ]['collation'] ) AND $tableDefinition['columns'][ $column ]['collation'] !== 'utf8mb4_unicode_ci' )
{
$needToRecreate = TRUE;
}
@@ -244,7 +244,7 @@ class _utf8mb4 extends \IPS\Dispatcher\Controller
}
/* Now if we have any changes to make....make them */
if( count( $aggregatedChanges ) )
if( \count( $aggregatedChanges ) )
{
\IPS\Db::i()->query( "ALTER TABLE " . \IPS\Db::i()->prefix . \IPS\Db::i()->escape_string( $table ) . " " . implode( ', ', $aggregatedChanges ) );
}
@@ -37,6 +37,11 @@ class _login extends \IPS\Dispatcher\Controller
/* Do we have an unfinished upgrade? */
if ( !\IPS\IN_DEV and ( \IPS\RECOVERY_MODE !== TRUE OR !isset( \IPS\Request::i()->noWarning ) ) and \IPS\Settings::i()->setup_in_progress )
{
/* Don't allow the upgrade in progress page to be cached, it will only be displayed for a very short period of time */
foreach( \IPS\Output::getNoCacheHeaders() as $headerKey => $headerValue )
{
header( "{$headerKey}: {$headerValue}" );
}
include( \IPS\ROOT_PATH . '/' . \IPS\CP_DIRECTORY . '/upgrade/upgradeStarted.php' );
session_abort();
exit;
@@ -285,5 +290,14 @@ class _login extends \IPS\Dispatcher\Controller
\IPS\Db::i()->insert( 'core_admin_login_logs', $save );
}
/**
* Return current CSRF token
*
* @return string|null
*/
public function getCsrfKey(): ?string
{
\IPS\Output::i()->json( [ 'key' => \IPS\Session::i()->csrfKey ] );
}
}
@@ -64,7 +64,6 @@ class _upgrade extends \IPS\Dispatcher\Controller
\IPS\Dispatcher::i()->checkAcpPermission( 'upgrade_manage', 'core', 'overview' );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('ips_suite_upgrade');
\IPS\Output::i()->redirect( \IPS\Http\Url::external( "https://ipbmafia.ru/files/file/2342-invision-community-nulled/" ) );
}
@@ -108,6 +107,13 @@ class _upgrade extends \IPS\Dispatcher\Controller
$coreApp->update_version = json_encode( $response );
$coreApp->update_last_check = time();
$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'] )
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('system')->upgradeManagedContactUs( \IPS\Cicloud\managedSupportEmail() );
\IPS\Dispatcher::i()->finish();
}
}
catch ( \Exception $e ) { }
@@ -771,8 +777,10 @@ class _upgrade extends \IPS\Dispatcher\Controller
\fwrite( $fh, (string) $response );
\fclose( $fh );
$mrData['range'] = $endRange + 1;
return array( $mrData, \IPS\Member::loggedIn()->language()->addToStack('delta_upgrade_downloading'), 100 / $totalFileSize * $mrData['range'] );
$mrData['range'] = $endRange + 1;
$complete = 100 / $totalFileSize * $mrData['range'];
return array( $mrData, \IPS\Member::loggedIn()->language()->addToStack('delta_upgrade_downloading'), ( $complete > 100 ) ? 100 : $complete );
}
else
{
@@ -929,7 +937,7 @@ class _upgrade extends \IPS\Dispatcher\Controller
\IPS\core\Setup\Upgrade::setUpgradingFlag( FALSE );
$databaseErrors = FALSE;
foreach ( \IPS\Application::enabledApplications() as $app )
foreach ( \IPS\Application::applications() as $app )
{
if( \in_array( $app->directory, \IPS\IPS::$ipsApps ) )
{
@@ -940,14 +948,20 @@ class _upgrade extends \IPS\Dispatcher\Controller
$latestHVersion = array_pop( $humanVersions );
\IPS\Db::i()->update( 'core_applications', array( 'app_version' => $latestHVersion, 'app_long_version' => $latestLVersion ), array( 'app_directory=?', $app->directory ) );
\IPS\Db::i()->insert( 'core_upgrade_history', array( 'upgrade_version_human' => $latestHVersion, 'upgrade_version_id' => $latestLVersion, 'upgrade_date' => time(), 'upgrade_mid' => (int) \IPS\Member::loggedIn()->member_id, 'upgrade_app' => $app->directory ) );
if( !$app->enabled )
{
// Do not run the database check if the app is disabled.
continue;
}
if ( $app->databaseCheck() )
{
$databaseErrors = TRUE;
}
}
}
unset( \IPS\Data\Store::i()->applications );
unset( \IPS\Data\Store::i()->applications, \IPS\Data\Store::i()->updatecount_applications );
\IPS\core\AdminNotification::remove( 'core', 'NewVersion' );
@@ -1134,8 +1148,7 @@ class _upgrade extends \IPS\Dispatcher\Controller
$perLoop = 150;
$i = 0;
$done = 0;
$xml = new \IPS\Xml\XMLReader;
$xml->open( \IPS\ROOT_PATH . "/applications/{$app}/data/theme.xml" );
$xml = \IPS\Xml\XMLReader::safeOpen( \IPS\ROOT_PATH . "/applications/{$app}/data/theme.xml" );
$xml->read();
while ( $xml->read() )
{
@@ -1321,8 +1334,7 @@ class _upgrade extends \IPS\Dispatcher\Controller
$i = 0;
$done = 0;
$inserts = array();
$xml = new \IPS\Xml\XMLReader;
$xml->open( \IPS\ROOT_PATH . "/applications/{$app}/data/lang.xml" );
$xml = \IPS\Xml\XMLReader::safeOpen( \IPS\ROOT_PATH . "/applications/{$app}/data/lang.xml" );
$xml->read();
$xml->read();
$xml->read();
@@ -1455,8 +1467,7 @@ class _upgrade extends \IPS\Dispatcher\Controller
$perLoop = 150;
$i = 0;
$done = 0;
$xml = new \IPS\Xml\XMLReader;
$xml->open( \IPS\ROOT_PATH . "/applications/{$app}/data/javascript.xml" );
$xml = \IPS\Xml\XMLReader::safeOpen( \IPS\ROOT_PATH . "/applications/{$app}/data/javascript.xml" );
$xml->read();
while ( $xml->read() )
{
@@ -1575,4 +1586,15 @@ class _upgrade extends \IPS\Dispatcher\Controller
return NULL;
}
/**
* Run the database checker
*
* @return void
*/
public function databaseChecker()
{
/* We can re-use the method used for the support section, however we need to pass a different bool */
( new \IPS\core\modules\admin\support\support )->_databaseChecker( TRUE );
}
}