2026-04-18 20:50:31 +00:00
< ? php
if ( ! defined ( 'ABSPATH' )) {
exit ;
}
class Ansico_Plugins_Admin {
public function __construct () {
add_action ( 'admin_menu' , array ( $this , 'register_admin_pages' ));
add_action ( 'admin_init' , array ( $this , 'register_settings' ));
add_action ( 'admin_init' , array ( $this , 'maybe_handle_reset_defaults' ));
add_action ( 'admin_notices' , array ( $this , 'maybe_show_test_notice' ));
add_action ( 'admin_notices' , array ( $this , 'maybe_show_reset_notice' ));
add_action ( 'admin_enqueue_scripts' , array ( $this , 'enqueue_admin_assets' ));
}
public function enqueue_admin_assets ( $hook_suffix ) {
if ( 'plugins_page_ansico-plugins-catalog' !== $hook_suffix ) {
return ;
}
add_thickbox ();
}
public function register_admin_pages () {
add_options_page (
__ ( 'Ansico Plugins' , 'ansico-plugins' ),
__ ( 'Ansico Plugins' , 'ansico-plugins' ),
'manage_options' ,
'ansico-plugins' ,
array ( $this , 'render_settings_page' )
);
add_plugins_page (
__ ( 'Ansico Plugins' , 'ansico-plugins' ),
__ ( 'Ansico Plugins' , 'ansico-plugins' ),
'install_plugins' ,
'ansico-plugins-catalog' ,
array ( $this , 'render_catalog_page' )
);
}
public function register_settings () {
register_setting ( 'ansico_plugins_group' , ANSICO_PLUGINS_OPTION , array ( $this , 'sanitize_settings' ));
add_settings_section (
'ansico_plugins_connection' ,
__ ( 'Forbindelse til Forgejo' , 'ansico-plugins' ),
'__return_false' ,
'ansico-plugins'
);
$fields = array (
'forgejo_base_url' => __ ( 'Forgejo base URL' , 'ansico-plugins' ),
'forgejo_owner' => __ ( 'Bruger eller organisation' , 'ansico-plugins' ),
'access_token' => __ ( 'Access token' , 'ansico-plugins' ),
'topic_filter' => __ ( 'Topic-filter' , 'ansico-plugins' ),
'request_timeout' => __ ( 'Timeout (sekunder)' , 'ansico-plugins' ),
);
foreach ( $fields as $key => $label ) {
add_settings_field ( $key , $label , array ( $this , 'render_field' ), 'ansico-plugins' , 'ansico_plugins_connection' , array ( 'key' => $key ));
}
add_settings_field ( 'owner_type' , __ ( 'Owner-type' , 'ansico-plugins' ), array ( $this , 'render_owner_type_field' ), 'ansico-plugins' , 'ansico_plugins_connection' );
add_settings_field ( 'verify_ssl' , __ ( 'SSL-verifikation' , 'ansico-plugins' ), array ( $this , 'render_verify_ssl_field' ), 'ansico-plugins' , 'ansico_plugins_connection' );
add_settings_section (
'ansico_plugins_self_update' ,
__ ( 'Opdatering af Ansico Plugins' , 'ansico-plugins' ),
'__return_false' ,
'ansico-plugins'
);
add_settings_field ( 'self_update_enabled' , __ ( 'Slå selvopdatering til' , 'ansico-plugins' ), array ( $this , 'render_self_update_enabled_field' ), 'ansico-plugins' , 'ansico_plugins_self_update' );
add_settings_field ( 'self_update_owner' , __ ( 'Ansico plugins owner' , 'ansico-plugins' ), array ( $this , 'render_field' ), 'ansico-plugins' , 'ansico_plugins_self_update' , array ( 'key' => 'self_update_owner' ));
add_settings_field ( 'self_update_repo' , __ ( 'Ansico plugins repository' , 'ansico-plugins' ), array ( $this , 'render_field' ), 'ansico-plugins' , 'ansico_plugins_self_update' , array ( 'key' => 'self_update_repo' ));
}
public function sanitize_settings ( $input ) {
$output = Ansico_Plugins :: get_settings ();
$output [ 'forgejo_base_url' ] = isset ( $input [ 'forgejo_base_url' ]) ? esc_url_raw ( trim (( string ) $input [ 'forgejo_base_url' ])) : '' ;
$output [ 'forgejo_owner' ] = isset ( $input [ 'forgejo_owner' ]) ? sanitize_text_field ( $input [ 'forgejo_owner' ]) : '' ;
$output [ 'owner_type' ] = isset ( $input [ 'owner_type' ]) && in_array ( $input [ 'owner_type' ], array ( 'user' , 'org' ), true ) ? $input [ 'owner_type' ] : 'org' ;
$output [ 'access_token' ] = isset ( $input [ 'access_token' ]) ? sanitize_text_field ( $input [ 'access_token' ]) : '' ;
$output [ 'topic_filter' ] = isset ( $input [ 'topic_filter' ]) ? sanitize_text_field ( $input [ 'topic_filter' ]) : 'wordpress-plugin' ;
$output [ 'verify_ssl' ] = ! empty ( $input [ 'verify_ssl' ]) ? 1 : 0 ;
$output [ 'request_timeout' ] = isset ( $input [ 'request_timeout' ]) ? max ( 5 , ( int ) $input [ 'request_timeout' ]) : 20 ;
$output [ 'self_update_enabled' ] = ! empty ( $input [ 'self_update_enabled' ]) ? 1 : 0 ;
$output [ 'self_update_owner' ] = isset ( $input [ 'self_update_owner' ]) ? sanitize_text_field ( $input [ 'self_update_owner' ]) : '' ;
$output [ 'self_update_repo' ] = isset ( $input [ 'self_update_repo' ]) ? sanitize_text_field ( $input [ 'self_update_repo' ]) : '' ;
Ansico_Plugins :: clear_http_cache ();
wp_clean_plugins_cache ( true );
delete_site_transient ( 'update_plugins' );
return $output ;
}
public function maybe_handle_reset_defaults () {
if ( ! is_admin () || ! current_user_can ( 'manage_options' ) || empty ( $_GET [ 'ansico_reset_defaults' ])) {
return ;
}
if ( ! isset ( $_GET [ '_wpnonce' ]) || ! wp_verify_nonce ( sanitize_text_field ( wp_unslash ( $_GET [ '_wpnonce' ])), 'ansico_plugins_reset_defaults' )) {
return ;
}
update_option ( ANSICO_PLUGINS_OPTION , Ansico_Plugins :: default_settings (), false );
Ansico_Plugins :: clear_http_cache ();
wp_clean_plugins_cache ( true );
delete_site_transient ( 'update_plugins' );
wp_safe_redirect ( admin_url ( 'options-general.php?page=ansico-plugins&ansico_reset_done=1' ));
exit ;
}
public function maybe_show_reset_notice () {
if ( ! current_user_can ( 'manage_options' ) || empty ( $_GET [ 'ansico_reset_done' ])) {
return ;
}
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__ ( 'Ansico standardindstillinger er gendannet.' , 'ansico-plugins' ) . '</p></div>' ;
}
public function render_field ( $args ) {
$settings = Ansico_Plugins :: get_settings ();
$key = $args [ 'key' ];
$value = isset ( $settings [ $key ]) ? $settings [ $key ] : '' ;
$type = 'text' ;
if ( 'access_token' === $key ) {
$type = 'password' ;
} elseif ( 'request_timeout' === $key ) {
$type = 'number' ;
} elseif ( 'forgejo_base_url' === $key ) {
$type = 'url' ;
}
printf (
'<input type="%1$s" class="regular-text" name="%2$s[%3$s]" value="%4$s" %5$s />' ,
esc_attr ( $type ),
esc_attr ( ANSICO_PLUGINS_OPTION ),
esc_attr ( $key ),
esc_attr ( $value ),
'request_timeout' === $key ? 'min="5" step="1"' : ''
);
$help = array (
'forgejo_base_url' => __ ( 'Eksempel: https://forgejo.ditdomæne.dk/' , 'ansico-plugins' ),
'forgejo_owner' => __ ( 'Navn på bruger eller organisation som ejer repositories.' , 'ansico-plugins' ),
'access_token' => __ ( 'PAT med adgang til at læse repos/releases. Kræves ofte for private repos.' , 'ansico-plugins' ),
'topic_filter' => __ ( 'Kun repos med dette topic vises. Brug tomt felt for at vise alle repos.' , 'ansico-plugins' ),
'request_timeout' => __ ( 'Øg værdien hvis Forgejo-serveren er langsom.' , 'ansico-plugins' ),
'self_update_owner' => __ ( 'Organisation eller bruger som ejer Ansico Plugins-repository.' , 'ansico-plugins' ),
'self_update_repo' => __ ( 'Repository-navn for Ansico Plugins, fx Ansico-plugins.' , 'ansico-plugins' ),
);
if ( isset ( $help [ $key ])) {
echo '<p class="description">' . esc_html ( $help [ $key ]) . '</p>' ;
}
}
public function render_owner_type_field () {
$settings = Ansico_Plugins :: get_settings ();
?>
< label >< input type = " radio " name = " <?php echo esc_attr(ANSICO_PLUGINS_OPTION); ?>[owner_type] " value = " user " < ? php checked ( $settings [ 'owner_type' ], 'user' ); ?> /> <?php esc_html_e('Bruger', 'ansico-plugins'); ?></label><br />
< label >< input type = " radio " name = " <?php echo esc_attr(ANSICO_PLUGINS_OPTION); ?>[owner_type] " value = " org " < ? php checked ( $settings [ 'owner_type' ], 'org' ); ?> /> <?php esc_html_e('Organisation', 'ansico-plugins'); ?></label>
< p class = " description " >< ? php esc_html_e ( 'Vælg om owner-feltet henviser til en bruger eller en organisation i Forgejo.' , 'ansico-plugins' ); ?> </p>
< ? php
}
public function render_verify_ssl_field () {
$settings = Ansico_Plugins :: get_settings ();
?>
< label >
< input type = " checkbox " name = " <?php echo esc_attr(ANSICO_PLUGINS_OPTION); ?>[verify_ssl] " value = " 1 " < ? php checked ( ! empty ( $settings [ 'verify_ssl' ])); ?> />
< ? php esc_html_e ( 'Verificér SSL-certifikat' , 'ansico-plugins' ); ?>
</ label >
< p class = " description " >< ? php esc_html_e ( 'Bør normalt være slået til. Slå kun fra ved selvsignerede certifikater i testmiljøer.' , 'ansico-plugins' ); ?> </p>
< ? php
}
public function render_self_update_enabled_field () {
$settings = Ansico_Plugins :: get_settings ();
?>
< label >
< input type = " checkbox " name = " <?php echo esc_attr(ANSICO_PLUGINS_OPTION); ?>[self_update_enabled] " value = " 1 " < ? php checked ( ! empty ( $settings [ 'self_update_enabled' ])); ?> />
< ? php esc_html_e ( 'Lad Ansico Plugins søge efter opdateringer til sig selv via et separat repository.' , 'ansico-plugins' ); ?>
</ label >
< p class = " description " >< ? php esc_html_e ( 'Bruger samme Forgejo base URL, token og SSL-indstillinger som ovenfor.' , 'ansico-plugins' ); ?> </p>
< ? php
}
public function render_settings_page () {
if ( ! current_user_can ( 'manage_options' )) {
return ;
}
$test_url = wp_nonce_url ( admin_url ( 'options-general.php?page=ansico-plugins&ansico_test_connection=1' ), 'ansico_plugins_test_connection' );
?>
< div class = " wrap " >
< h1 >< ? php esc_html_e ( 'Ansico Plugins' , 'ansico-plugins' ); ?> </h1>
< p >< ? php esc_html_e ( " Forbind WordPress Ansico's Forgejo-server og få adgang til Ansico plugins. " , 'ansico-plugins' ); ?> </p>
< form method = " post " action = " options.php " >
< ? php
settings_fields ( 'ansico_plugins_group' );
do_settings_sections ( 'ansico-plugins' );
submit_button ( __ ( 'Gem indstillinger' , 'ansico-plugins' ));
$reset_url = wp_nonce_url ( admin_url ( 'options-general.php?page=ansico-plugins&ansico_reset_defaults=1' ), 'ansico_plugins_reset_defaults' );
echo ' <a href="' . esc_url ( $reset_url ) . '" class="button button-secondary" onclick="return confirm(\'' . esc_js ( __ ( 'Nulstil til Ansico standardindstillinger?' , 'ansico-plugins' )) . '\');">' . esc_html__ ( 'Reset til Ansico standard' , 'ansico-plugins' ) . '</a>' ;
?>
</ form >
< p >
< a href = " <?php echo esc_url( $test_url ); ?> " class = " button button-secondary " >< ? php esc_html_e ( 'Test forbindelse' , 'ansico-plugins' ); ?> </a>
< a href = " <?php echo esc_url(admin_url('plugins.php?page=ansico-plugins-catalog')); ?> " class = " button button-primary " >< ? php esc_html_e ( 'Åbn plugin-katalog' , 'ansico-plugins' ); ?> </a>
</ p >
< hr />
< h2 >< ? php esc_html_e ( 'Sådan kommer du i gang' , 'ansico-plugins' ); ?> </h2>
< ol >
< li >< ? php esc_html_e ( 'Indtast base URL til Forgejo, fx https://forgejo.ditdomæne.dk/.' , 'ansico-plugins' ); ?> </li>
< li >< ? php esc_html_e ( 'Angiv bruger eller organisation som ejer de repositories, der skal vises.' , 'ansico-plugins' ); ?> </li>
< li >< ? php esc_html_e ( 'Opret et access token i Forgejo med læseadgang til repos og releases, hvis repos er private.' , 'ansico-plugins' ); ?> </li>
< li >< ? php esc_html_e ( 'Tilføj topic på dine plugin-repositories, fx wordpress-plugin, så kataloget kan filtrere dem.' , 'ansico-plugins' ); ?> </li>
< li >< ? php esc_html_e ( 'Upload en installérbar ZIP som release-asset på hver release. Kataloget vælger første ZIP-fil i seneste release.' , 'ansico-plugins' ); ?> </li>
< li >< ? php esc_html_e ( 'Installer første gang via Plugins → Ansico Plugins. Derefter kan WordPress selv opdage nye versioner for de plugins, der er installeret via Ansico Plugins.' , 'ansico-plugins' ); ?> </li>
< li >< ? php esc_html_e ( 'Du kan også slå selvopdatering til for Ansico Plugins via et separat repository under sektionen nedenfor.' , 'ansico-plugins' ); ?> </li>
</ ol >
</ div >
< ? php
}
public function maybe_show_test_notice () {
if ( ! current_user_can ( 'manage_options' ) || empty ( $_GET [ 'ansico_test_connection' ])) {
return ;
}
if ( ! isset ( $_GET [ '_wpnonce' ]) || ! wp_verify_nonce ( sanitize_text_field ( wp_unslash ( $_GET [ '_wpnonce' ])), 'ansico_plugins_test_connection' )) {
return ;
}
$client = new Ansico_Plugins_Client ();
$result = $client -> test_connection ();
if ( is_wp_error ( $result )) {
printf (
'<div class="notice notice-error"><p><strong>%s</strong> %s</p></div>' ,
esc_html__ ( 'Forgejo-forbindelse fejlede:' , 'ansico-plugins' ),
esc_html ( $result -> get_error_message ())
);
return ;
}
printf (
'<div class="notice notice-success"><p>%s <strong>%s</strong></p></div>' ,
esc_html__ ( 'Forbindelse OK. Forgejo-version:' , 'ansico-plugins' ),
esc_html ( $result [ 'version' ])
);
}
public function render_catalog_page () {
if ( ! current_user_can ( 'install_plugins' )) {
return ;
}
$client = new Ansico_Plugins_Client ();
$repos = $client -> get_repositories ();
if ( isset ( $_GET [ 'ansico_view' ]) && 'details' === sanitize_key ( wp_unslash ( $_GET [ 'ansico_view' ]))) {
$this -> render_details_page ( $client , $repos , false );
return ;
}
$settings = Ansico_Plugins :: get_settings ();
$owner_label = ! empty ( $settings [ 'forgejo_owner' ]) ? $settings [ 'forgejo_owner' ] : __ ( 'din organisation' , 'ansico-plugins' );
$forgejo_link = ! empty ( $settings [ 'forgejo_base_url' ]) ? trailingslashit ( $settings [ 'forgejo_base_url' ]) . rawurlencode ( $owner_label ) : '' ;
?>
< div class = " wrap ansico-plugins-wrap " >
< ? php $this -> render_catalog_styles (); ?>
< h1 class = " wp-heading-inline " >< ? php esc_html_e ( 'Ansico Plugins' , 'ansico-plugins' ); ?> </h1>
< a href = " <?php echo esc_url(admin_url('options-general.php?page=ansico-plugins')); ?> " class = " page-title-action " >< ? php esc_html_e ( 'Indstillinger' , 'ansico-plugins' ); ?> </a>
< hr class = " wp-header-end " />
< div class = " ansico-directory-header " >
< div >
< p >< ? php printf (
wp_kses (
__ ( 'Installer, aktivér, geninstallér, opdatér eller afinstallér dine interne plugins direkte fra Forgejo releases fra <a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s</a>.' , 'ansico-plugins' ),
array ( 'a' => array ( 'href' => array (), 'target' => array (), 'rel' => array ()))
),
esc_url ( $forgejo_link ),
esc_html ( $owner_label )
); ?> </p>
< ? php echo $this -> get_self_plugin_status_markup ( $client ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</ div >
</ div >
< ? php
if ( is_wp_error ( $repos )) {
printf ( '<div class="notice notice-error"><p>%s</p></div>' , esc_html ( $repos -> get_error_message ()));
echo '<p><a class="button" href="' . esc_url ( admin_url ( 'options-general.php?page=ansico-plugins' )) . '">' . esc_html__ ( 'Gå til indstillinger' , 'ansico-plugins' ) . '</a></p>' ;
echo '</div>' ;
return ;
}
if ( empty ( $repos )) {
echo '<div class="notice notice-info"><p>' . esc_html__ ( 'Ingen repositories matchede de valgte kriterier.' , 'ansico-plugins' ) . '</p></div>' ;
echo '<p>' . esc_html__ ( 'Forgejo svarer, men ingen repos matchede topic-filteret. Kontrollér topic-navn og gem indstillingerne igen for at rydde cache.' , 'ansico-plugins' ) . '</p>' ;
echo '</div>' ;
return ;
}
if ( ! function_exists ( 'get_plugins' )) {
require_once ABSPATH . 'wp-admin/includes/plugin.php' ;
}
$installed_plugins = get_plugins ();
$managed_plugins = Ansico_Plugins :: get_managed_plugins ();
echo '<div class="ansico-plugin-grid">' ;
foreach ( $repos as $repo ) {
$release = $client -> get_latest_release ( $repo [ 'owner' ][ 'login' ], $repo [ 'name' ]);
$zip_asset = ! is_wp_error ( $release ) ? $client -> find_zip_asset ( $release ) : null ;
$plugin_state = $this -> get_plugin_state ( $repo , $installed_plugins , $managed_plugins , $release );
$metadata = $client -> get_repository_plugin_metadata ( $repo [ 'owner' ][ 'login' ], $repo [ 'name' ]);
$metadata = is_wp_error ( $metadata ) ? array () : $metadata ;
$display_name = ! empty ( $metadata [ 'readme_title' ]) ? $metadata [ 'readme_title' ] : $repo [ 'name' ];
$details_url = $this -> get_details_url ( $repo [ 'name' ]);
$badge = $this -> get_state_badge ( $plugin_state );
$version_label = is_wp_error ( $release ) ? '—' : $this -> normalize_release_version ( $release );
echo '<div class="ansico-plugin-card">' ;
echo '<div class="ansico-plugin-card__top">' ;
2026-04-18 21:19:08 +00:00
echo $this -> get_plugin_icon_markup ( $metadata , $display_name );
2026-04-18 20:50:31 +00:00
echo '<div class="ansico-plugin-meta">' ;
echo '<h2><a href="' . esc_url ( $details_url ) . '" class="thickbox">' . esc_html ( $display_name ) . '</a></h2>' ;
echo '<div class="ansico-plugin-submeta">' . $badge . '</div>' ;
echo '</div></div>' ;
2026-04-18 21:19:08 +00:00
echo '<p class="ansico-plugin-description">' . esc_html ( ! empty ( $metadata [ 'description' ]) ? $metadata [ 'description' ] : ( ! empty ( $repo [ 'description' ]) ? $repo [ 'description' ] : __ ( 'Ingen beskrivelse angivet.' , 'ansico-plugins' ))) . '</p>' ;
2026-04-18 20:50:31 +00:00
echo '<div class="ansico-plugin-facts">' ;
echo '<span><strong>' . esc_html__ ( 'Installeret' , 'ansico-plugins' ) . ':</strong> ' . esc_html ( $plugin_state [ 'installed_version' ] !== '' ? $plugin_state [ 'installed_version' ] : '—' ) . '</span>' ;
echo '<span><strong>' . esc_html__ ( 'Seneste' , 'ansico-plugins' ) . ':</strong> ' . esc_html ( $version_label ) . '</span>' ;
echo '</div>' ;
echo '<div class="ansico-plugin-actions">' ;
foreach ( $this -> build_action_buttons ( $repo , $plugin_state , $zip_asset ) as $button_html ) {
echo $button_html ; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '<a class="button-link thickbox" href="' . esc_url ( $details_url ) . '">' . esc_html__ ( 'Vis detaljer' , 'ansico-plugins' ) . '</a>' ;
echo '</div>' ;
echo $this -> get_details_inline_markup ( $repo , $release , $zip_asset , $plugin_state , $metadata ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>' ;
}
echo '</div></div>' ;
}
private function render_details_page ( $client , $repos , $is_modal = false ) {
$repo_name = isset ( $_GET [ 'repo' ]) ? sanitize_text_field ( wp_unslash ( $_GET [ 'repo' ])) : '' ;
$repo = null ;
if ( ! is_wp_error ( $repos )) {
foreach ( $repos as $candidate ) {
if ( ! empty ( $candidate [ 'name' ]) && $candidate [ 'name' ] === $repo_name ) {
$repo = $candidate ;
break ;
}
}
}
if ( ! $repo ) {
wp_die ( esc_html__ ( 'Repository blev ikke fundet.' , 'ansico-plugins' ));
}
if ( ! function_exists ( 'get_plugins' )) {
require_once ABSPATH . 'wp-admin/includes/plugin.php' ;
}
$installed_plugins = get_plugins ();
$managed_plugins = Ansico_Plugins :: get_managed_plugins ();
$release = $client -> get_latest_release ( $repo [ 'owner' ][ 'login' ], $repo [ 'name' ]);
$zip_asset = ! is_wp_error ( $release ) ? $client -> find_zip_asset ( $release ) : null ;
$plugin_state = $this -> get_plugin_state ( $repo , $installed_plugins , $managed_plugins , $release );
$metadata = $client -> get_repository_plugin_metadata ( $repo [ 'owner' ][ 'login' ], $repo [ 'name' ]);
$metadata = is_wp_error ( $metadata ) ? array () : $metadata ;
$back_url = admin_url ( 'plugins.php?page=ansico-plugins-catalog' );
echo '<div class="wrap ansico-plugins-wrap">' ;
$this -> render_catalog_styles ();
echo '<h1 class="wp-heading-inline">' . esc_html ( ! empty ( $metadata [ 'readme_title' ]) ? $metadata [ 'readme_title' ] : $repo [ 'name' ]) . '</h1>' ;
echo '<a href="' . esc_url ( $back_url ) . '" class="page-title-action">' . esc_html__ ( 'Tilbage til katalog' , 'ansico-plugins' ) . '</a>' ;
echo '<hr class="wp-header-end" />' ;
echo $this -> get_details_content_markup ( $repo , $release , $zip_asset , $plugin_state , $metadata ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>' ;
exit ;
}
private function build_action_buttons ( $repo , $plugin_state , $zip_asset ) {
$buttons = array ();
$repo_name = $repo [ 'name' ];
$slug = $repo [ 'name' ];
if ( ! $plugin_state [ 'is_installed' ] && $zip_asset ) {
$buttons [] = '<a class="button button-primary" href="' . esc_url ( $this -> build_action_url ( 'install_latest' , $repo_name , $slug )) . '">' . esc_html__ ( 'Installér' , 'ansico-plugins' ) . '</a>' ;
return $buttons ;
}
if ( $plugin_state [ 'is_installed' ] && ! $plugin_state [ 'is_active' ]) {
$buttons [] = '<a class="button button-primary" href="' . esc_url ( wp_nonce_url ( admin_url ( 'plugins.php?action=activate&plugin=' . rawurlencode ( $plugin_state [ 'plugin_file' ])), 'activate-plugin_' . $plugin_state [ 'plugin_file' ])) . '">' . esc_html__ ( 'Aktivér' , 'ansico-plugins' ) . '</a>' ;
$buttons [] = '<a class="button button-secondary" href="' . esc_url ( $this -> build_action_url ( 'delete_plugin' , $repo_name , $slug , $plugin_state [ 'plugin_file' ])) . '" onclick="return confirm(\'' . esc_js ( __ ( 'Er du sikker på, at pluginet skal afinstalleres?' , 'ansico-plugins' )) . '\');">' . esc_html__ ( 'Afinstallér' , 'ansico-plugins' ) . '</a>' ;
return $buttons ;
}
if ( $plugin_state [ 'is_installed' ] && $plugin_state [ 'has_update' ]) {
$buttons [] = '<a class="button button-primary" href="' . esc_url ( $this -> build_action_url ( 'install_latest' , $repo_name , $slug )) . '">' . esc_html__ ( 'Opdatér' , 'ansico-plugins' ) . '</a>' ;
$buttons [] = '<a class="button button-secondary" href="' . esc_url ( $this -> build_action_url ( 'delete_plugin' , $repo_name , $slug , $plugin_state [ 'plugin_file' ])) . '" onclick="return confirm(\'' . esc_js ( __ ( 'Er du sikker på, at pluginet skal afinstalleres?' , 'ansico-plugins' )) . '\');">' . esc_html__ ( 'Afinstallér' , 'ansico-plugins' ) . '</a>' ;
return $buttons ;
}
if ( $plugin_state [ 'is_installed' ] && $plugin_state [ 'is_active' ]) {
$buttons [] = '<a class="button button-primary" href="' . esc_url ( $this -> build_action_url ( 'install_latest' , $repo_name , $slug )) . '">' . esc_html__ ( 'Geninstallér' , 'ansico-plugins' ) . '</a>' ;
$buttons [] = '<a class="button button-secondary" href="' . esc_url ( $this -> build_action_url ( 'delete_plugin' , $repo_name , $slug , $plugin_state [ 'plugin_file' ])) . '" onclick="return confirm(\'' . esc_js ( __ ( 'Er du sikker på, at pluginet skal afinstalleres?' , 'ansico-plugins' )) . '\');">' . esc_html__ ( 'Afinstallér' , 'ansico-plugins' ) . '</a>' ;
}
return $buttons ;
}
private function get_plugin_state ( $repo , $installed_plugins , $managed_plugins , $release ) {
$plugin_file = '' ;
foreach ( $managed_plugins as $candidate_plugin_file => $meta ) {
if ( ! empty ( $meta [ 'owner' ]) && ! empty ( $meta [ 'repo' ]) && $meta [ 'owner' ] === $repo [ 'owner' ][ 'login' ] && $meta [ 'repo' ] === $repo [ 'name' ]) {
$plugin_file = $candidate_plugin_file ;
break ;
}
}
if ( '' === $plugin_file ) {
foreach ( $installed_plugins as $candidate_plugin_file => $plugin_data ) {
if ( dirname ( $candidate_plugin_file ) === sanitize_title ( $repo [ 'name' ])) {
$plugin_file = $candidate_plugin_file ;
break ;
}
}
}
$is_installed = '' !== $plugin_file && isset ( $installed_plugins [ $plugin_file ]);
$installed_version = $is_installed && ! empty ( $installed_plugins [ $plugin_file ][ 'Version' ]) ? ( string ) $installed_plugins [ $plugin_file ][ 'Version' ] : '' ;
$is_active = $is_installed ? is_plugin_active ( $plugin_file ) : false ;
$remote_version = ! is_wp_error ( $release ) ? $this -> normalize_release_version ( $release ) : '' ;
$has_update = $is_installed && '' !== $installed_version && '' !== $remote_version && version_compare ( $remote_version , $installed_version , '>' );
return array (
'plugin_file' => $plugin_file ,
'is_installed' => $is_installed ,
'is_active' => $is_active ,
'installed_version' => $installed_version ,
'remote_version' => $remote_version ,
'has_update' => $has_update ,
);
}
private function normalize_release_version ( $release ) {
$version = '' ;
if ( ! empty ( $release [ 'tag_name' ])) {
$version = ( string ) $release [ 'tag_name' ];
} elseif ( ! empty ( $release [ 'name' ])) {
$version = ( string ) $release [ 'name' ];
}
$version = ltrim ( $version , 'vV' );
return '' !== $version ? $version : '—' ;
}
private function get_state_badge ( $plugin_state ) {
if ( ! $plugin_state [ 'is_installed' ]) {
if ( empty ( $plugin_state [ 'remote_version' ]) || '—' === $plugin_state [ 'remote_version' ]) {
return '<span class="ansico-badge ansico-badge--neutral">' . esc_html__ ( 'Ikke udgivet' , 'ansico-plugins' ) . '</span>' ;
}
return '<span class="ansico-badge ansico-badge--neutral">' . esc_html__ ( 'Ikke installeret' , 'ansico-plugins' ) . '</span>' ;
}
if ( $plugin_state [ 'has_update' ]) {
return '<span class="ansico-badge ansico-badge--warning">' . esc_html__ ( 'Opdatering klar' , 'ansico-plugins' ) . '</span>' ;
}
if ( ! $plugin_state [ 'is_active' ]) {
return '<span class="ansico-badge ansico-badge--muted">' . esc_html__ ( 'Installeret' , 'ansico-plugins' ) . '</span>' ;
}
return '<span class="ansico-badge ansico-badge--success">' . esc_html__ ( 'Aktiv' , 'ansico-plugins' ) . '</span>' ;
}
private function get_self_plugin_status_markup ( $client ) {
$settings = Ansico_Plugins :: get_settings ();
if ( empty ( $settings [ 'self_update_enabled' ]) || empty ( $settings [ 'self_update_owner' ]) || empty ( $settings [ 'self_update_repo' ])) {
return '' ;
}
if ( ! function_exists ( 'get_plugin_data' )) {
require_once ABSPATH . 'wp-admin/includes/plugin.php' ;
}
$plugin_file = plugin_basename ( ANSICO_PLUGINS_FILE );
$plugin_data = get_plugin_data ( ANSICO_PLUGINS_FILE , false , false );
$current_version = ! empty ( $plugin_data [ 'Version' ]) ? ( string ) $plugin_data [ 'Version' ] : ANSICO_PLUGINS_VERSION ;
$release = $client -> get_latest_release ( $settings [ 'self_update_owner' ], $settings [ 'self_update_repo' ]);
if ( is_wp_error ( $release )) {
return '<p class="ansico-self-status ansico-self-status--error">' . esc_html__ ( 'Ansico Plugins: kunne ikke hente opdateringsstatus.' , 'ansico-plugins' ) . '</p>' ;
}
$remote_version = $this -> normalize_release_version ( $release );
$repo_url = trailingslashit ( $settings [ 'forgejo_base_url' ]) . rawurlencode ( $settings [ 'self_update_owner' ]) . '/' . rawurlencode ( $settings [ 'self_update_repo' ]);
if ( '' === $remote_version || '—' === $remote_version ) {
return '<p class="ansico-self-status">' . esc_html__ ( 'Ansico Plugins: ingen udgivet version fundet endnu.' , 'ansico-plugins' ) . '</p>' ;
}
if ( version_compare ( $remote_version , $current_version , '>' )) {
$update_url = esc_url ( admin_url ( 'plugins.php' ));
return '<p class="ansico-self-status ansico-self-status--warning">' . sprintf (
wp_kses (
__ ( 'Ansico Plugins: version %1$s er installeret, og version %2$s er tilgængelig fra <a href="%3$s" target="_blank" rel="noopener noreferrer">%4$s</a>. Opdater fra den almindelige <a href="%5$s">Plugins-side</a>.' , 'ansico-plugins' ),
array ( 'a' => array ( 'href' => array (), 'target' => array (), 'rel' => array ()))
),
esc_html ( $current_version ),
esc_html ( $remote_version ),
esc_url ( $repo_url ),
esc_html ( $settings [ 'self_update_repo' ]),
esc_url ( $update_url )
) . '</p>' ;
}
return '<p class="ansico-self-status ansico-self-status--success">' . sprintf (
wp_kses (
__ ( 'Ansico Plugins er opdateret. Installeret version %1$s matcher seneste release i <a href="%2$s" target="_blank" rel="noopener noreferrer">%3$s</a>.' , 'ansico-plugins' ),
array ( 'a' => array ( 'href' => array (), 'target' => array (), 'rel' => array ()))
),
esc_html ( $current_version ),
esc_url ( $repo_url ),
esc_html ( $settings [ 'self_update_repo' ])
) . '</p>' ;
}
private function get_details_url ( $repo_name ) {
return '#TB_inline?width=920&height=648&inlineId=ansico-plugin-details-' . rawurlencode ( sanitize_title ( $repo_name ));
}
private function build_action_url ( $action , $repo_name , $slug , $plugin_file = '' ) {
$url = admin_url ( 'plugins.php?page=ansico-plugins-catalog&ansico_action=' . rawurlencode ( $action ) . '&repo=' . rawurlencode ( $repo_name ) . '&slug=' . rawurlencode ( $slug ));
if ( '' !== $plugin_file ) {
$url .= '&plugin_file=' . rawurlencode ( $plugin_file );
}
return wp_nonce_url ( $url , 'ansico_plugins_action' );
}
private function get_details_inline_markup ( $repo , $release , $zip_asset , $plugin_state , $metadata ) {
$id = 'ansico-plugin-details-' . sanitize_title ( $repo [ 'name' ]);
return '<div id="' . esc_attr ( $id ) . '" style="display:none;">' .
'<div class="ansico-thickbox-content">' .
$this -> get_details_content_markup ( $repo , $release , $zip_asset , $plugin_state , $metadata ) .
'</div></div>' ;
}
private function get_details_content_markup ( $repo , $release , $zip_asset , $plugin_state , $metadata ) {
$display_name = ! empty ( $metadata [ 'readme_title' ]) ? $metadata [ 'readme_title' ] : $repo [ 'name' ];
2026-04-18 21:19:08 +00:00
$description = ! empty ( $metadata [ 'description' ]) ? $metadata [ 'description' ] : ( ! empty ( $repo [ 'description' ]) ? $repo [ 'description' ] : __ ( 'Ingen beskrivelse angivet.' , 'ansico-plugins' ));
$sections = ! empty ( $metadata [ 'readme_sections' ]) && is_array ( $metadata [ 'readme_sections' ]) ? $metadata [ 'readme_sections' ] : array ();
$is_wordpress_readme = ! empty ( $metadata [ 'readme_source' ]) && preg_match ( '/\.txt$/i' , $metadata [ 'readme_source' ]);
2026-04-18 20:50:31 +00:00
$html = '' ;
2026-04-18 21:19:08 +00:00
if ( ! empty ( $metadata [ 'banner_url' ])) {
$html .= '<div class="ansico-plugin-banner"><img src="' . esc_url ( $metadata [ 'banner_url' ]) . '" alt="' . esc_attr ( $display_name ) . '" /></div>' ;
}
2026-04-18 20:50:31 +00:00
$html .= '<div class="ansico-plugin-hero">' ;
2026-04-18 21:19:08 +00:00
$html .= $this -> get_plugin_icon_markup ( $metadata , $display_name );
2026-04-18 20:50:31 +00:00
$html .= '<div>' ;
$html .= '<h1 style="margin:0 0 8px;font-size:26px;line-height:1.2">' . esc_html ( $display_name ) . '</h1>' ;
2026-04-18 21:19:08 +00:00
$html .= '<p class="description" style="margin:0 0 10px">' . esc_html ( $description ) . '</p>' ;
2026-04-18 20:50:31 +00:00
$html .= $this -> get_state_badge ( $plugin_state );
$html .= '</div></div>' ;
$html .= '<div class="ansico-modal-actions">' ;
foreach ( $this -> build_action_buttons ( $repo , $plugin_state , $zip_asset ) as $button_html ) {
$html .= $button_html ;
}
$html .= '<a class="button" href="' . esc_url ( $repo [ 'html_url' ]) . '" target="_blank" rel="noopener noreferrer">' . esc_html__ ( 'Åbn repository' , 'ansico-plugins' ) . '</a>' ;
$html .= '</div>' ;
$meta_rows = array (
__ ( 'Installeret version' , 'ansico-plugins' ) => $plugin_state [ 'installed_version' ] !== '' ? $plugin_state [ 'installed_version' ] : '—' ,
__ ( 'Seneste release' , 'ansico-plugins' ) => ! is_wp_error ( $release ) ? $this -> normalize_release_version ( $release ) : '—' ,
__ ( 'Repository' , 'ansico-plugins' ) => ! empty ( $repo [ 'full_name' ]) ? $repo [ 'full_name' ] : $repo [ 'name' ],
__ ( 'ZIP-fil' , 'ansico-plugins' ) => $zip_asset && ! empty ( $zip_asset [ 'name' ]) ? $zip_asset [ 'name' ] : '—' ,
__ ( 'Forfatter' , 'ansico-plugins' ) => ! empty ( $metadata [ 'author_name' ]) ? $metadata [ 'author_name' ] : '—' ,
__ ( 'Forfatter-URL' , 'ansico-plugins' ) => ! empty ( $metadata [ 'author_url' ]) ? $metadata [ 'author_url' ] : '—' ,
__ ( 'Support-URL' , 'ansico-plugins' ) => ! empty ( $metadata [ 'support_url' ]) ? $metadata [ 'support_url' ] : '—' ,
__ ( 'Licens' , 'ansico-plugins' ) => ! empty ( $metadata [ 'license' ]) ? $metadata [ 'license' ] : '—' ,
__ ( 'Plugin-URL' , 'ansico-plugins' ) => ! empty ( $metadata [ 'plugin_headers' ][ 'PluginURI' ]) ? $metadata [ 'plugin_headers' ][ 'PluginURI' ] : '—' ,
__ ( 'Kræver WordPress' , 'ansico-plugins' ) => ! empty ( $metadata [ 'plugin_headers' ][ 'RequiresWP' ]) ? $metadata [ 'plugin_headers' ][ 'RequiresWP' ] : '—' ,
__ ( 'Kræver PHP' , 'ansico-plugins' ) => ! empty ( $metadata [ 'plugin_headers' ][ 'RequiresPHP' ]) ? $metadata [ 'plugin_headers' ][ 'RequiresPHP' ] : '—' ,
);
2026-04-18 21:19:08 +00:00
if ( ! empty ( $metadata [ 'plugin_headers' ][ 'TextDomain' ])) {
$meta_rows [ __ ( 'Text domain' , 'ansico-plugins' )] = $metadata [ 'plugin_headers' ][ 'TextDomain' ];
}
$tabs = array ();
$tabs [] = array (
'slug' => 'description' ,
'label' => __ ( 'Beskrivelse' , 'ansico-plugins' ),
'content' => $this -> render_readme_section ( ! empty ( $sections [ 'description' ]) ? $sections [ 'description' ] : $description , $is_wordpress_readme ),
);
if ( ! empty ( $sections [ 'installation' ])) {
$tabs [] = array (
'slug' => 'installation' ,
'label' => __ ( 'Installation' , 'ansico-plugins' ),
'content' => $this -> render_readme_section ( $sections [ 'installation' ], $is_wordpress_readme ),
);
}
if ( ! empty ( $sections [ 'changelog' ])) {
$tabs [] = array (
'slug' => 'changelog' ,
'label' => __ ( 'Changelog' , 'ansico-plugins' ),
'content' => $this -> render_readme_section ( $sections [ 'changelog' ], $is_wordpress_readme ),
);
}
if ( ! empty ( $metadata [ 'screenshot_url' ]) || ! empty ( $sections [ 'screenshots' ])) {
$screen = '' ;
if ( ! empty ( $metadata [ 'screenshot_url' ])) {
$screen .= '<p><img src="' . esc_url ( $metadata [ 'screenshot_url' ]) . '" alt="' . esc_attr ( sprintf ( __ ( 'Screenshot for %s' , 'ansico-plugins' ), $display_name )) . '" /></p>' ;
}
if ( ! empty ( $sections [ 'screenshots' ])) {
$screen .= $this -> render_readme_section ( $sections [ 'screenshots' ], $is_wordpress_readme );
}
$tabs [] = array (
'slug' => 'screenshots' ,
'label' => __ ( 'Screenshots' , 'ansico-plugins' ),
'content' => $screen ,
);
}
$tabs [] = array (
'slug' => 'metadata' ,
'label' => __ ( 'Metadata' , 'ansico-plugins' ),
'content' => $this -> render_metadata_grid ( $meta_rows ),
);
if ( ! is_wp_error ( $release ) && ! empty ( $release [ 'body' ])) {
$tabs [] = array (
'slug' => 'release-notes' ,
'label' => __ ( 'Release-noter' , 'ansico-plugins' ),
'content' => '<div class="ansico-release-notes">' . wp_kses_post ( wpautop ( $release [ 'body' ])) . '</div>' ,
);
}
$html .= $this -> render_details_tabs ( $tabs , $repo [ 'name' ]);
return $html ;
}
private function get_plugin_icon_markup ( $metadata , $display_name ) {
if ( ! empty ( $metadata [ 'icon_url' ])) {
return '<div class="ansico-plugin-icon ansico-plugin-icon--image"><img src="' . esc_url ( $metadata [ 'icon_url' ]) . '" alt="' . esc_attr ( $display_name ) . '" /></div>' ;
}
return '<div class="ansico-plugin-icon">AP</div>' ;
}
private function render_metadata_grid ( $meta_rows ) {
$html = '<div class="ansico-meta-grid">' ;
2026-04-18 20:50:31 +00:00
foreach ( $meta_rows as $label => $value ) {
$html .= '<p><strong>' . esc_html ( $label ) . '</strong><br>' ;
if ( is_string ( $value ) && preg_match ( '#^https?://#i' , $value )) {
$html .= '<a href="' . esc_url ( $value ) . '" target="_blank" rel="noopener noreferrer">' . esc_html ( $value ) . '</a>' ;
} else {
$html .= esc_html ( $value );
}
$html .= '</p>' ;
}
$html .= '</div>' ;
2026-04-18 21:19:08 +00:00
return $html ;
}
2026-04-18 20:50:31 +00:00
2026-04-18 21:19:08 +00:00
private function render_details_tabs ( $tabs , $repo_name ) {
$id_base = 'ansico-tabs-' . sanitize_title ( $repo_name );
$html = '<div class="ansico-tabs"><div class="ansico-tab-nav" role="tablist">' ;
foreach ( $tabs as $index => $tab ) {
$active = 0 === $index ? ' is-active' : '' ;
$html .= '<button type="button" class="ansico-tab-button' . $active . '" data-target="#' . esc_attr ( $id_base . '-' . $tab [ 'slug' ]) . '">' . esc_html ( $tab [ 'label' ]) . '</button>' ;
}
$html .= '</div>' ;
foreach ( $tabs as $index => $tab ) {
$active = 0 === $index ? ' is-active' : '' ;
$html .= '<div id="' . esc_attr ( $id_base . '-' . $tab [ 'slug' ]) . '" class="ansico-tab-panel' . $active . '">' . $tab [ 'content' ] . '</div>' ;
2026-04-18 20:50:31 +00:00
}
2026-04-18 21:19:08 +00:00
$html .= '</div>' ;
return $html ;
}
2026-04-18 20:50:31 +00:00
2026-04-18 21:19:08 +00:00
private function render_readme_section ( $content , $is_wordpress_readme = false ) {
$content = trim (( string ) $content );
if ( '' === $content ) {
return '<p>' . esc_html__ ( 'Ingen oplysninger tilgængelige.' , 'ansico-plugins' ) . '</p>' ;
}
if ( $is_wordpress_readme ) {
return wp_kses_post ( wpautop ( $this -> format_wordpress_readme_text ( $content )));
2026-04-18 20:50:31 +00:00
}
2026-04-18 21:19:08 +00:00
return wp_kses_post ( wpautop ( $this -> format_markdown_text ( $content )));
}
2026-04-18 20:50:31 +00:00
2026-04-18 21:19:08 +00:00
private function format_wordpress_readme_text ( $content ) {
$content = preg_replace ( '/^=\s*(.+?)\s*=\s*$/m' , "
< strong > $ 1 </ strong >
" , $content );
$content = preg_replace ( '/^\*\s+/m' , '• ' , $content );
return $content ;
}
private function format_markdown_text ( $content ) {
$content = preg_replace ( '/^###\s+(.+)$/m' , "
< strong > $ 1 </ strong >
" , $content );
$content = preg_replace ( '/^##\s+(.+)$/m' , "
< strong > $ 1 </ strong >
" , $content );
$content = preg_replace ( '/^#\s+(.+)$/m' , "
< strong > $ 1 </ strong >
" , $content );
$content = preg_replace ( '/^[-*]\s+/m' , '• ' , $content );
$content = preg_replace ( '/`([^`]+)`/' , '$1' , $content );
return $content ;
2026-04-18 20:50:31 +00:00
}
private function render_catalog_styles () {
echo ' < style >
. ansico - directory - header { display : flex ; justify - content : space - between ; align - items : center ; margin : 16 px 0 20 px }
. ansico - directory - header p { margin : 0 ; color : #50575e;font-size:14px}
. ansico - self - status { margin - top : 10 px ! important ; font - size : 13 px ; color : #50575e}
. ansico - self - status -- success { color : #008a20}
. ansico - self - status -- warning { color : #8a6d00}
. ansico - self - status -- error { color : #b32d2e}
. ansico - plugin - grid { display : grid ; grid - template - columns : repeat ( auto - fill , minmax ( 320 px , 1 fr )); gap : 20 px ; margin - top : 18 px }
. ansico - plugin - card { background : #fff;border:1px solid #dcdcde;border-radius:10px;padding:18px;box-shadow:0 1px 2px rgba(0,0,0,.04);display:flex;flex-direction:column;gap:14px}
. ansico - plugin - card__top { display : flex ; gap : 14 px ; align - items : flex - start }
2026-04-18 21:19:08 +00:00
. ansico - plugin - icon { width : 64 px ; height : 64 px ; border - radius : 12 px ; background : #2271b1;color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:20px;flex-shrink:0;overflow:hidden}.ansico-plugin-icon--image{background:#fff;border:1px solid #dcdcde}.ansico-plugin-icon img{width:100%;height:100%;object-fit:cover;display:block}
2026-04-18 20:50:31 +00:00
. ansico - plugin - meta h2 { margin : 0 0 6 px ; font - size : 18 px ; line - height : 1.3 }
. ansico - plugin - meta h2 a { text - decoration : none }
. ansico - plugin - submeta { display : flex ; gap : 8 px ; align - items : center ; flex - wrap : wrap ; color : #646970}
. ansico - plugin - description { margin : 0 ; color : #1d2327;min-height:48px}
. ansico - plugin - facts { display : grid ; grid - template - columns : 1 fr ; gap : 6 px ; font - size : 13 px ; color : #50575e}
. ansico - plugin - actions { display : flex ; gap : 10 px ; align - items : center ; flex - wrap : wrap ; margin - top : auto }
. ansico - badge { display : inline - flex ; align - items : center ; padding : 3 px 8 px ; border - radius : 999 px ; font - size : 12 px ; font - weight : 600 }
. ansico - badge -- neutral { background : #f0f0f1;color:#3c434a}
. ansico - badge -- muted { background : #f6f7f7;color:#50575e}
. ansico - badge -- success { background : #edfaef;color:#008a20}
. ansico - badge -- warning { background : #fcf9e8;color:#8a6d00}
. ansico - plugin - hero { display : flex ; gap : 16 px ; align - items : flex - start ; margin - bottom : 18 px }
. ansico - plugin - hero . ansico - plugin - icon { width : 72 px ; height : 72 px ; font - size : 22 px }
. ansico - meta - grid { display : grid ; grid - template - columns : 1 fr 1 fr ; gap : 10 px 20 px ; margin : 18 px 0 }
. ansico - meta - grid p { margin : 0 }
2026-04-18 21:19:08 +00:00
. ansico - plugin - banner { margin : 0 0 18 px } . ansico - plugin - banner img { width : 100 % ; height : auto ; border - radius : 10 px ; border : 1 px solid #dcdcde;display:block}.ansico-screenshot img,.ansico-tab-panel img{max-width:100%;height:auto;border:1px solid #dcdcde;border-radius:8px}
2026-04-18 20:50:31 +00:00
. ansico - modal - actions { display : flex ; gap : 10 px ; flex - wrap : wrap ; margin : 18 px 0 }
. ansico - release - notes { background : #f6f7f7;border-radius:8px;padding:14px}
2026-04-18 21:19:08 +00:00
. ansico - thickbox - content { padding : 20 px 24 px 24 px } . ansico - tabs { margin - top : 18 px } . ansico - tab - nav { display : flex ; gap : 8 px ; flex - wrap : wrap ; border - bottom : 1 px solid #dcdcde;margin-bottom:16px;padding-bottom:8px}.ansico-tab-button{background:#f6f7f7;border:1px solid #dcdcde;border-radius:6px 6px 0 0;padding:8px 12px;cursor:pointer}.ansico-tab-button.is-active{background:#fff;border-bottom-color:#fff;font-weight:600}.ansico-tab-panel{display:none}.ansico-tab-panel.is-active{display:block}
2026-04-18 20:50:31 +00:00
#TB_window{max-height:81vh !important}#TB_ajaxContent{max-height:calc(81vh - 45px) !important;height:auto !important;overflow:auto !important}#TB_ajaxContent .ansico-thickbox-content{width:auto !important;height:auto !important;max-height:calc(81vh - 65px) !important;overflow:auto !important;padding:20px 24px 24px !important;box-sizing:border-box}
@ media ( max - width : 960 px ){ . ansico - meta - grid { grid - template - columns : 1 fr }}
2026-04-18 21:19:08 +00:00
</ style >< script > document . addEventListener ( " click " , function ( e ){ var btn = e . target . closest ( " .ansico-tab-button " ); if ( ! btn ){ return ;} var wrap = btn . closest ( " .ansico-tabs " ); if ( ! wrap ){ return ;} var targetSel = btn . getAttribute ( " data-target " ); wrap . querySelectorAll ( " .ansico-tab-button " ) . forEach ( function ( el ){ el . classList . remove ( " is-active " );}); wrap . querySelectorAll ( " .ansico-tab-panel " ) . forEach ( function ( el ){ el . classList . remove ( " is-active " );}); btn . classList . add ( " is-active " ); var panel = wrap . querySelector ( targetSel ); if ( panel ){ panel . classList . add ( " is-active " );}}); </ script > ' ;
2026-04-18 20:50:31 +00:00
}
}