Ansico-plugins/ansico-plugins/includes/class-ansico-plugins-admin.php

659 lines
38 KiB
PHP
Raw Permalink Normal View History

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">';
echo '<div class="ansico-plugin-icon">AP</div>';
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>';
echo '<p class="ansico-plugin-description">' . esc_html(!empty($repo['description']) ? $repo['description'] : __('Ingen beskrivelse angivet.', 'ansico-plugins')) . '</p>';
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'];
$html = '';
$html .= '<div class="ansico-plugin-hero">';
$html .= '<div class="ansico-plugin-icon">AP</div>';
$html .= '<div>';
$html .= '<h1 style="margin:0 0 8px;font-size:26px;line-height:1.2">' . esc_html($display_name) . '</h1>';
$html .= '<p class="description" style="margin:0 0 10px">' . esc_html(!empty($repo['description']) ? $repo['description'] : __('Ingen beskrivelse angivet.', 'ansico-plugins')) . '</p>';
$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>';
$html .= '<div class="ansico-meta-grid">';
$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'] : '—',
__('Text domain', 'ansico-plugins') => !empty($metadata['plugin_headers']['TextDomain']) ? $metadata['plugin_headers']['TextDomain'] : '—',
);
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>';
if (!empty($metadata['screenshot_url'])) {
$html .= '<div class="ansico-screenshot" style="margin:22px 0">';
$html .= '<h2>' . esc_html__('Screenshot', 'ansico-plugins') . '</h2>';
$html .= '<img src="' . esc_url($metadata['screenshot_url']) . '" alt="' . esc_attr(sprintf(__('Screenshot for %s', 'ansico-plugins'), $display_name)) . '" />';
$html .= '</div>';
}
if (!is_wp_error($release) && !empty($release['body'])) {
$html .= '<h2>' . esc_html__('Release-noter', 'ansico-plugins') . '</h2>';
$html .= '<div class="ansico-release-notes">' . wp_kses_post(wpautop($release['body'])) . '</div>';
}
return $html;
}
private function render_catalog_styles() {
echo '<style>
.ansico-directory-header{display:flex;justify-content:space-between;align-items:center;margin:16px 0 20px}
.ansico-directory-header p{margin:0;color:#50575e;font-size:14px}
.ansico-self-status{margin-top:10px !important;font-size:13px;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(320px,1fr));gap:20px;margin-top:18px}
.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:14px;align-items:flex-start}
.ansico-plugin-icon{width:64px;height:64px;border-radius:12px;background:#2271b1;color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:20px;flex-shrink:0}
.ansico-plugin-meta h2{margin:0 0 6px;font-size:18px;line-height:1.3}
.ansico-plugin-meta h2 a{text-decoration:none}
.ansico-plugin-submeta{display:flex;gap:8px;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:1fr;gap:6px;font-size:13px;color:#50575e}
.ansico-plugin-actions{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:auto}
.ansico-badge{display:inline-flex;align-items:center;padding:3px 8px;border-radius:999px;font-size:12px;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:16px;align-items:flex-start;margin-bottom:18px}
.ansico-plugin-hero .ansico-plugin-icon{width:72px;height:72px;font-size:22px}
.ansico-meta-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px 20px;margin:18px 0}
.ansico-meta-grid p{margin:0}
.ansico-screenshot img{max-width:100%;height:auto;border:1px solid #dcdcde;border-radius:8px}
.ansico-modal-actions{display:flex;gap:10px;flex-wrap:wrap;margin:18px 0}
.ansico-release-notes{background:#f6f7f7;border-radius:8px;padding:14px}
.ansico-thickbox-content{padding:20px 24px 24px}
#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: 960px){.ansico-meta-grid{grid-template-columns:1fr}}
</style>';
}
}