Invision Power Services, Inc. * @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 */ 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( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' ); exit; } /** * Achievement badges */ class badges extends Controller { /** * @brief Has been CSRF-protected */ public static bool $csrfProtected = TRUE; /** * Node Class */ protected string $nodeClass = 'IPS\core\Achievements\Badge'; /** * Execute * * @return void */ public function execute() : void { Dispatcher::i()->checkAcpPermission( 'badges_manage' ); parent::execute(); } /** * Manage * * @return void */ protected function manage() : void { if( $data = Rule::getRebuildProgress() ) { Output::i()->output .= Theme::i()->getTemplate( 'achievements', 'core' )->rebuildProgress( $data, TRUE ); } Output::i()->sidebar['actions']['export'] = array( 'primary' => false, 'icon' => 'cloud-download', '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 ( Member::loggedIn()->hasAcpRestriction( 'core', 'achievements', 'badges_manage' ) ) { Output::i()->sidebar['actions']['import'] = array( 'primary' => false, 'icon' => 'cloud-upload', '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' ); parent::manage(); } /** * Fetch any additional HTML for this row * * @param object $node Node returned from $nodeClass::load() * @return NULL|string */ public function _getRowHtml( object $node ): ?string { $count = Db::i()->select( 'COUNT(*)', 'core_member_badges', [ 'badge=?', $node->id ] )->first(); $url = Url::internal("app=core&module=achievements&controller=badges&do=view&id={$node->id}"); return Theme::i()->getTemplate('achievements')->memberCount( $url, $count ); } /** * View who has earned a badge * * @return void */ public function view() : void { $badge = Badge::load( Request::i()->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', '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' => SEARCH_DATE_RANGE, ); $table->parsers = array( 'member' => function ( $val, $row ) { return Theme::i()->getTemplate( 'global', 'core' )->userLink( Member::load( $val ) ); }, 'action_log' => function( $val, $row ) { try { if ( $row['action'] ) { $exploded = explode( '_', $row['action'] ); $extension = Application::load( $exploded[0] )->extensions( 'core', 'AchievementAction' )[ $exploded[1] ]; return $extension->logRow( $row['identifier'], explode( ',', $row['actor'] ) ); } else { throw new OutOfRangeException; } } catch( OutOfRangeException $e ) { return Member::loggedIn()->language()->addToStack('unknown'); } }, 'rule' => function( $val, $row ) { $rule = NULL; if ( $val ) { try { $rule = Rule::load( $val ); } catch ( OutOfRangeException $e ) { } } if ( $rule ) { 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 Member::loggedIn()->language()->addToStack('unknown'); } }, 'datetime' => function( $val ) { return DateTime::ts( $val ); } ); Output::i()->title = $badge->_title; Output::i()->output = $table; } /** * Import dialog * * @return void */ public function exportForm() : void { Dispatcher::i()->checkAcpPermission( 'badges_manage' ); $assignedBadges = Badge::getAssignedBadgeIds(); $where = NULL; if ( count( $assignedBadges ) ) { $where = [ Db::i()->in( '`id`', $assignedBadges, TRUE ) ]; } $exportableBadges = Db::i()->select( 'COUNT(*)', 'core_badges', $where )->first(); /* Display */ Output::i()->output = Theme::i()->getTemplate( 'achievements', 'core' )->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() : void { $xml = SimpleXML::create('badges'); $langs = []; 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']; } $assignedBadges = Badge::getAssignedBadgeIds(); $where = NULL; if ( count( $assignedBadges ) ) { $where = [ Db::i()->in( '`id`', $assignedBadges, TRUE ) ]; } /* Ranks */ foreach ( Db::i()->select( '*', 'core_badges', $where ) as $badge ) { try { $icon = 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 ) { } } Session::i()->log( 'acplogs__exported_badges' ); Output::i()->sendOutput( $xml->asXML(), 200, 'application/xml', array( 'Content-Disposition' => Output::getContentDisposition( 'attachment', "Achievement_Badges.xml" ) ) ); } /** * Import from upload * * @return void */ public function import() : void { Session::i()->csrfCheck(); if ( !file_exists( Request::i()->file ) or md5_file( Request::i()->file ) !== Request::i()->key ) { Output::i()->error( 'generic_error', '3C130/1', 500, '' ); } try { Badge::importXml( Request::i()->file, ( ! empty( Request::i()->wipe ) ) ); } catch( Exception $e ) { Output::i()->error( $e->getMessage(), '2C422/1', 403, '' ); } Session::i()->log( 'acplogs__imported_badges' ); Output::i()->redirect( Url::internal( 'app=core&module=achievements&controller=badges' ), 'completed' ); } /** * Import dialog * * @return void */ public function importForm() : void { $form = new Form( 'form', 'acp_achievements_import' ); $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( TEMP_DIRECTORY, 'IPS' ); move_uploaded_file( $values['acp_achievements_import_xml'], $tempFile ); /* Initate a redirector */ 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 */ 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'); $badge = new CustomBadge; $badge->shape = Request::i()->shape ?: 'circle'; $badge->foreground = Request::i()->foreground ?: '#fff'; $badge->background = Request::i()->background ?: '#eeb95f'; $badge->border = Request::i()->border ?: '#f7d36f'; $badge->icon = Request::i()->icon ? json_decode( Request::i()->icon, true ) : null; $badge->rotation = Request::i()->rotation ?: 0; $badge->sides = Request::i()->sides ?: 5; $badge->number_overlay = Request::i()->numberoverlay ?: 0; $badge->icon_size = Request::i()->iconsize ?: 3; $badge->generateSVG( true ); echo $badge->raw; exit(); } }