Version 5.0.0 beta 1

This commit is contained in:
Neo committed 2025-12-19 16:27:35 -08:00
1 parent 25ddeb65d6
commit 15c7beabc5
6736 files changed
+627902 -497943

No files matched your search

@@ -11,35 +11,63 @@
namespace IPS\core\modules\admin\achievements;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use Exception;
use IPS\Application;
use IPS\core\Achievements\Badge;
use IPS\core\Achievements\Rule;
use IPS\core\CustomBadge;
use IPS\DateTime;
use IPS\Db;
use IPS\Dispatcher;
use IPS\File;
use IPS\Helpers\Form;
use IPS\Helpers\Form\Upload;
use IPS\Helpers\Form\YesNo;
use IPS\Http\Url;
use IPS\Member;
use IPS\Node\Controller;
use IPS\Output;
use IPS\Request;
use IPS\Session;
use IPS\Theme;
use IPS\Xml\SimpleXML;
use OutOfRangeException;
use function count;
use function defined;
use function mb_substr;
use const IPS\Helpers\Table\SEARCH_DATE_RANGE;
use const IPS\TEMP_DIRECTORY;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Achievement badges
*/
class _badges extends \IPS\Node\Controller
class badges extends Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
public static bool $csrfProtected = TRUE;
/**
* Node Class
*/
protected $nodeClass = 'IPS\core\Achievements\Badge';
protected string $nodeClass = 'IPS\core\Achievements\Badge';
/**
* Execute
*
* @return void
*/
public function execute()
public function execute() : void
{
\IPS\Dispatcher::i()->checkAcpPermission( 'badges_manage' );
Dispatcher::i()->checkAcpPermission( 'badges_manage' );
parent::execute();
}
@@ -48,34 +76,35 @@ class _badges extends \IPS\Node\Controller
*
* @return void
*/
protected function manage()
protected function manage() : void
{
if( $data = \IPS\core\Achievements\Rule::getRebuildProgress() )
if( $data = Rule::getRebuildProgress() )
{
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'achievements' )->rebuildProgress( $data, TRUE );
Output::i()->output .= Theme::i()->getTemplate( 'achievements' )->rebuildProgress( $data, TRUE );
}
\IPS\Output::i()->sidebar['actions']['export'] = array(
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') ),
'link' => Url::internal('app=core&module=achievements&controller=badges&do=exportForm'),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => Member::loggedIn()->language()->addToStack('acp_achievements_export') ),
'title' => 'acp_achievements_export',
);
if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'achievements', 'badges_manage' ) )
if ( Member::loggedIn()->hasAcpRestriction( 'core', 'achievements', 'badges_manage' ) )
{
\IPS\Output::i()->sidebar['actions']['import'] = array(
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') ),
'link' => Url::internal('app=core&module=achievements&controller=badges&do=importForm'),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => Member::loggedIn()->language()->addToStack('acp_achievements_import') ),
'title' => 'acp_achievements_import',
);
}
Output::i()->output .= Theme::i()->getTemplate( 'forms', 'core' )->blurb( 'cheev_badges_blurb' );
return parent::manage();
parent::manage();
}
/**
@@ -84,13 +113,13 @@ class _badges extends \IPS\Node\Controller
* @param object $node Node returned from $nodeClass::load()
* @return NULL|string
*/
public function _getRowHtml( $node )
public function _getRowHtml( object $node ): ?string
{
$count = \IPS\Db::i()->select( 'COUNT(*)', 'core_member_badges', [ 'badge=?', $node->id ] )->first();
$count = 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}");
$url = Url::internal("app=core&module=achievements&controller=badges&do=view&id={$node->id}");
return \IPS\Theme::i()->getTemplate('achievements')->memberCount( $url, $count );
return Theme::i()->getTemplate('achievements')->memberCount( $url, $count );
}
/**
@@ -98,11 +127,11 @@ class _badges extends \IPS\Node\Controller
*
* @return void
*/
public function view()
public function view() : void
{
$badge = \IPS\core\Achievements\Badge::load( \IPS\Request::i()->id );
$badge = Badge::load( 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 = new \IPS\Helpers\Table\Db( 'core_member_badges', 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',
@@ -114,13 +143,13 @@ class _badges extends \IPS\Node\Controller
$table->sortBy = $table->sortBy ?: 'datetime';
$table->advancedSearch = array(
'datetime' => \IPS\Helpers\Table\SEARCH_DATE_RANGE,
'datetime' => SEARCH_DATE_RANGE,
);
$table->parsers = array(
'member' => function ( $val, $row )
{
return \IPS\Theme::i()->getTemplate( 'global', 'core' )->userLink( \IPS\Member::load( $val ) );
return Theme::i()->getTemplate( 'global', 'core' )->userLink( Member::load( $val ) );
},
'action_log' => function( $val, $row ) {
try
@@ -128,17 +157,17 @@ class _badges extends \IPS\Node\Controller
if ( $row['action'] )
{
$exploded = explode( '_', $row['action'] );
$extension = \IPS\Application::load( $exploded[0] )->extensions( 'core', 'AchievementAction' )[ $exploded[1] ];
$extension = Application::load( $exploded[0] )->extensions( 'core', 'AchievementAction' )[ $exploded[1] ];
return $extension->logRow( $row['identifier'], explode( ',', $row['actor'] ) );
}
else
{
throw new \OutOfRangeException;
throw new OutOfRangeException;
}
}
catch( \OutOfRangeException $e )
catch( OutOfRangeException $e )
{
return \IPS\Member::loggedIn()->language()->addToStack('unknown');
return Member::loggedIn()->language()->addToStack('unknown');
}
},
'rule' => function( $val, $row ) {
@@ -147,28 +176,28 @@ class _badges extends \IPS\Node\Controller
{
try
{
$rule = \IPS\core\Achievements\Rule::load( $val );
$rule = Rule::load( $val );
}
catch ( \OutOfRangeException $e ) { }
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 );
return Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( 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');
return Member::loggedIn()->language()->addToStack('unknown');
}
},
'datetime' => function( $val )
{
return \IPS\DateTime::ts( $val );
return DateTime::ts( $val );
}
);
\IPS\Output::i()->title = $badge->_title;
\IPS\Output::i()->output = $table;
Output::i()->title = $badge->_title;
Output::i()->output = $table;
}
/**
@@ -176,53 +205,53 @@ class _badges extends \IPS\Node\Controller
*
* @return void
*/
public function exportForm()
public function exportForm() : void
{
\IPS\Dispatcher::i()->checkAcpPermission( 'badges_manage' );
$assignedBadges = \IPS\core\Achievements\Badge::getAssignedBadgeIds();
Dispatcher::i()->checkAcpPermission( 'badges_manage' );
$assignedBadges = Badge::getAssignedBadgeIds();
$where = NULL;
if ( \count( $assignedBadges ) )
if ( count( $assignedBadges ) )
{
$where = [ \IPS\Db::i()->in( '`id`', $assignedBadges, TRUE ) ];
$where = [ Db::i()->in( '`id`', $assignedBadges, TRUE ) ];
}
$exportableBadges = \IPS\Db::i()->select( 'COUNT(*)', 'core_badges', $where )->first();
$exportableBadges = Db::i()->select( 'COUNT(*)', 'core_badges', $where )->first();
/* Display */
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'achievements' )->badgeExport( $exportableBadges );
Output::i()->output = 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
* @throws Exception
*/
public function export()
public function export() : void
{
$xml = \IPS\Xml\SimpleXML::create('badges');
$xml = SimpleXML::create('badges');
$langs = [];
foreach( \IPS\Db::i()->select('word_key, word_default', 'core_sys_lang_words', [ "lang_id=? AND word_key LIKE 'core_badges_%'", \IPS\Member::loggedIn()->language()->id ] ) as $row )
foreach( Db::i()->select('word_key, word_default', 'core_sys_lang_words', [ "lang_id=? AND word_key LIKE 'core_badges_%'", Member::loggedIn()->language()->id ] ) as $row )
{
$langs[ \mb_substr( $row['word_key'], 12 ) ] = $row['word_default'];
$langs[ mb_substr( $row['word_key'], 12 ) ] = $row['word_default'];
}
$assignedBadges = \IPS\core\Achievements\Badge::getAssignedBadgeIds();
$assignedBadges = Badge::getAssignedBadgeIds();
$where = NULL;
if ( \count( $assignedBadges ) )
if ( count( $assignedBadges ) )
{
$where = [ \IPS\Db::i()->in( '`id`', $assignedBadges, TRUE ) ];
$where = [ Db::i()->in( '`id`', $assignedBadges, TRUE ) ];
}
/* Ranks */
foreach ( \IPS\Db::i()->select( '*', 'core_badges', $where ) as $badge )
foreach ( Db::i()->select( '*', 'core_badges', $where ) as $badge )
{
try
{
$icon = \IPS\File::get( 'core_Badges', $badge['image'] );
$icon = File::get( 'core_Badges', $badge['image'] );
$forXml = [
'manually_awarded' => $badge['manually_awarded'],
@@ -233,12 +262,12 @@ class _badges extends \IPS\Node\Controller
$xml->addChild( 'badge', $forXml );
}
catch( \Exception $e ) { }
catch( Exception $e ) { }
}
\IPS\Session::i()->log( 'acplogs__exported_badges' );
Session::i()->log( 'acplogs__exported_badges' );
\IPS\Output::i()->sendOutput( $xml->asXML(), 200, 'application/xml', array( 'Content-Disposition' => \IPS\Output::getContentDisposition( 'attachment', "Achievement_Badges.xml" ) ) );
Output::i()->sendOutput( $xml->asXML(), 200, 'application/xml', array( 'Content-Disposition' => Output::getContentDisposition( 'attachment', "Achievement_Badges.xml" ) ) );
}
/**
@@ -246,27 +275,27 @@ class _badges extends \IPS\Node\Controller
*
* @return void
*/
public function import()
public function import() : void
{
\IPS\Session::i()->csrfCheck();
Session::i()->csrfCheck();
if ( !file_exists( \IPS\Request::i()->file ) or md5_file( \IPS\Request::i()->file ) !== \IPS\Request::i()->key )
if ( !file_exists( Request::i()->file ) or md5_file( Request::i()->file ) !== Request::i()->key )
{
\IPS\Output::i()->error( 'generic_error', '3C130/1', 500, '' );
Output::i()->error( 'generic_error', '3C130/1', 500, '' );
}
try
{
\IPS\core\Achievements\Badge::importXml( \IPS\Request::i()->file, ( ! empty( \IPS\Request::i()->wipe ) ) );
Badge::importXml( Request::i()->file, ( ! empty( Request::i()->wipe ) ) );
}
catch( \Exception $e )
catch( Exception $e )
{
\IPS\Output::i()->error( $e->getMessage(), '2C422/1', 403, '' );
Output::i()->error( $e->getMessage(), '2C422/1', 403, '' );
}
\IPS\Session::i()->log( 'acplogs__imported_badges' );
Session::i()->log( 'acplogs__imported_badges' );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=achievements&controller=badges' ), 'completed' );
Output::i()->redirect( Url::internal( 'app=core&module=achievements&controller=badges' ), 'completed' );
}
/**
@@ -274,24 +303,41 @@ class _badges extends \IPS\Node\Controller
*
* @return void
*/
public function importForm()
public function importForm() : void
{
$form = new \IPS\Helpers\Form( 'form', 'acp_achievements_import' );
$form = new 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' ) );
$form->add( new YesNo( 'acp_achievements_import_option_badge_wipe', 0, FALSE, [], NULL, NULL, NULL, 'acp_achievements_import_option_rule_wipe' ) );
$form->add( new 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' );
$tempFile = tempnam( 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() );
Output::i()->redirect( 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 );
Output::i()->output = Theme::i()->getTemplate( 'global' )->block( Member::loggedIn()->language()->addToStack('acp_achievements_import'), $form, FALSE );
}
public function badgePreview() : void
{
header('content-type: image/svg+xml');
echo new CustomBadge(
Request::i()->shape ?: 'circle',
Request::i()->foreground ?: '#fff',
Request::i()->background ?: '#eeb95f',
Request::i()->border ?: '#f7d36f',
Request::i()->icon ?: null,
(int) Request::i()->rotation ?: 0,
(int) Request::i()->sides ?: 5,
(int) Request::i()->numberoverlay ?: 0
);
exit();
}
}