diff --git a/ansico-plugins-0.0.0.35.zip b/ansico-plugins-0.0.0.35.zip new file mode 100644 index 0000000..f99b4e2 Binary files /dev/null and b/ansico-plugins-0.0.0.35.zip differ diff --git a/ansico-plugins-0.0.0.35.zip:Zone.Identifier b/ansico-plugins-0.0.0.35.zip:Zone.Identifier new file mode 100644 index 0000000..d6c1ec6 Binary files /dev/null and b/ansico-plugins-0.0.0.35.zip:Zone.Identifier differ diff --git a/ansico-plugins/README.md b/ansico-plugins/README.md new file mode 100644 index 0000000..6cd67c6 --- /dev/null +++ b/ansico-plugins/README.md @@ -0,0 +1,62 @@ +# Ansico Plugins + +Version: 0.0.0.1 +Forfatter: Andreas Andersen (Ansico) +URL: https://ansico.dk + +## Formål + +Dette plugin gør det muligt at forbinde et WordPress-site til en Forgejo-server og bruge udvalgte repositories som et privat plugin-katalog. + +## Hvad kan pluginet i denne version? + +- Gemme forbindelsesoplysninger til Forgejo +- Teste forbindelsen til Forgejo +- Hente repositories fra en bruger eller organisation +- Filtrere repositories på topic +- Vise seneste release for hvert repository +- Finde første ZIP-asset i seneste release +- Installere pluginet direkte fra den ZIP-fil + +## Sådan opsætter du det + +1. Installer og aktivér pluginet i WordPress. +2. Gå til **Indstillinger → Ansico Plugins**. +3. Udfyld: + - **Forgejo base URL** – fx `https://forgejo.example.com/` + - **Bruger eller organisation** – den konto der ejer repos + - **Owner-type** – `Bruger` eller `Organisation` + - **Access token** – nødvendigt for private repos + - **Topic-filter** – fx `wordpress-plugin` +4. Gem indstillinger. +5. Klik på **Test forbindelse**. +6. Gå til **Plugins → Ansico Plugins**. +7. Klik på **Installér / opdatér** ud for det ønskede plugin. + +## Krav til releases på Forgejo + +Hver plugin-release bør have en ZIP-asset som: +- ender på `.zip` +- indeholder plugin-mappen i roden +- indeholder en gyldig WordPress-pluginfil + +Eksempel: + +`mit-plugin.zip` +- `mit-plugin/` +- `mit-plugin/mit-plugin.php` +- `mit-plugin/includes/...` + +## Begrænsninger i version 0.0.0.1 + +- Pluginet bruger første ZIP-fil i seneste release +- Ingen automatisk versionssammenligning i WordPress’ normale update-UI endnu +- Ingen changelog-modal endnu +- Ingen cachelagring af API-kald endnu + +## Næste oplagte trin + +- Integrere med WordPress’ normale plugin-opdateringer +- Understøtte manifest-fil pr. repo +- Understøtte changelog og plugin-detaljer +- Tilføje caching og bedre fejlvisning diff --git a/ansico-plugins/ansico-plugins.php b/ansico-plugins/ansico-plugins.php new file mode 100644 index 0000000..536c75e --- /dev/null +++ b/ansico-plugins/ansico-plugins.php @@ -0,0 +1,147 @@ +admin = new Ansico_Plugins_Admin(); + $this->installer = new Ansico_Plugins_Installer(); + $this->updater = new Ansico_Plugins_Updater(); + + add_action('admin_init', array($this, 'maybe_handle_actions')); + add_filter('plugin_action_links_' . plugin_basename(__FILE__), array($this, 'plugin_action_links')); + } + + public static function default_settings() { + return array( + 'forgejo_base_url' => 'https://ansico.dk', + 'forgejo_owner' => 'Ansico', + 'owner_type' => 'org', + 'access_token' => '', + 'topic_filter' => 'wordpress-plugin', + 'verify_ssl' => 1, + 'request_timeout' => 20, + 'self_update_enabled' => 1, + 'self_update_owner' => 'Ansico', + 'self_update_repo' => 'Ansico-plugins', + ); + + } + + public static function get_settings() { + $settings = get_option(ANSICO_PLUGINS_OPTION, array()); + return wp_parse_args($settings, self::default_settings()); + } + + + public static function clear_http_cache() { + global $wpdb; + $like = $wpdb->esc_like('_transient_ansico_plugins_') . '%'; + $like_timeout = $wpdb->esc_like('_transient_timeout_ansico_plugins_') . '%'; + $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s", $like, $like_timeout)); + } + + public static function get_managed_plugins() { + $managed = get_option(ANSICO_PLUGINS_MANAGED_OPTION, array()); + return is_array($managed) ? $managed : array(); + } + + public static function set_managed_plugin($plugin_file, $meta) { + $managed = self::get_managed_plugins(); + $managed[$plugin_file] = wp_parse_args($meta, array( + 'slug' => dirname($plugin_file), + 'owner' => '', + 'repo' => '', + 'description' => '', + 'repo_html_url' => '', + )); + update_option(ANSICO_PLUGINS_MANAGED_OPTION, $managed, false); + } + + public function plugin_action_links($links) { + $settings_link = sprintf( + '%s', + esc_url(admin_url('options-general.php?page=ansico-plugins')), + esc_html__('Indstillinger', 'ansico-plugins') + ); + array_unshift($links, $settings_link); + return $links; + } + + public function maybe_handle_actions() { + if (!is_admin() || !current_user_can('install_plugins')) { + return; + } + + if (empty($_GET['ansico_action'])) { + return; + } + + $action = sanitize_key(wp_unslash($_GET['ansico_action'])); + + if (!isset($_GET['_wpnonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'])), 'ansico_plugins_action')) { + wp_die(esc_html__('Ugyldig sikkerheds-token.', 'ansico-plugins')); + } + + if ('install_latest' === $action) { + $repo = isset($_GET['repo']) ? sanitize_text_field(wp_unslash($_GET['repo'])) : ''; + $slug = isset($_GET['slug']) ? sanitize_title(wp_unslash($_GET['slug'])) : ''; + $this->installer->install_latest_release($repo, $slug); + } + + if ('delete_plugin' === $action) { + $repo = isset($_GET['repo']) ? sanitize_text_field(wp_unslash($_GET['repo'])) : ''; + $plugin_file = isset($_GET['plugin_file']) ? sanitize_text_field(wp_unslash($_GET['plugin_file'])) : ''; + $this->installer->delete_plugin($repo, $plugin_file); + } + } +} + +register_activation_hook(__FILE__, function() { + if (!get_option(ANSICO_PLUGINS_OPTION)) { + add_option(ANSICO_PLUGINS_OPTION, Ansico_Plugins::default_settings()); + } + if (!get_option(ANSICO_PLUGINS_MANAGED_OPTION)) { + add_option(ANSICO_PLUGINS_MANAGED_OPTION, array()); + } +}); + +Ansico_Plugins::instance(); diff --git a/ansico-plugins/includes/class-ansico-plugins-admin.php b/ansico-plugins/includes/class-ansico-plugins-admin.php new file mode 100644 index 0000000..37b1faa --- /dev/null +++ b/ansico-plugins/includes/class-ansico-plugins-admin.php @@ -0,0 +1,658 @@ + __('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 '
' . esc_html__('Ansico standardindstillinger er gendannet.', 'ansico-plugins') . '
' . esc_html($help[$key]) . '
'; + } + } + + public function render_owner_type_field() { + $settings = Ansico_Plugins::get_settings(); + ?> +%s %s
%s %s
%2$s.', 'ansico-plugins'), + array('a' => array('href' => array(), 'target' => array(), 'rel' => array())) + ), + esc_url($forgejo_link), + esc_html($owner_label) + ); ?>
+ get_self_plugin_status_markup($client); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> +%s
' . esc_html__('Gå til indstillinger', 'ansico-plugins') . '
'; + echo ''; + return; + } + + if (empty($repos)) { + echo '' . esc_html__('Ingen repositories matchede de valgte kriterier.', 'ansico-plugins') . '
' . esc_html__('Forgejo svarer, men ingen repos matchede topic-filteret. Kontrollér topic-navn og gem indstillingerne igen for at rydde cache.', 'ansico-plugins') . '
'; + echo ''; + 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 '' . esc_html(!empty($repo['description']) ? $repo['description'] : __('Ingen beskrivelse angivet.', 'ansico-plugins')) . '
'; + + echo '' . esc_html__('Ansico Plugins: kunne ikke hente opdateringsstatus.', 'ansico-plugins') . '
'; + } + + $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 '' . esc_html__('Ansico Plugins: ingen udgivet version fundet endnu.', 'ansico-plugins') . '
'; + } + + if (version_compare($remote_version, $current_version, '>')) { + $update_url = esc_url(admin_url('plugins.php')); + return '' . sprintf( + wp_kses( + __('Ansico Plugins: version %1$s er installeret, og version %2$s er tilgængelig fra %4$s. Opdater fra den almindelige Plugins-side.', '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) + ) . '
'; + } + + return '' . sprintf( + wp_kses( + __('Ansico Plugins er opdateret. Installeret version %1$s matcher seneste release i %3$s.', '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']) + ) . '
'; + } + + 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 ''; + } + + 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 .= '' . esc_html(!empty($repo['description']) ? $repo['description'] : __('Ingen beskrivelse angivet.', 'ansico-plugins')) . '
'; + $html .= $this->get_state_badge($plugin_state); + $html .= '' . esc_html($message) . '
' . esc_html__('Tilbage til Ansico Plugins', 'ansico-plugins') . '
'; + echo '' . esc_html($is_update ? __('Plugin opdateret.', 'ansico-plugins') : __('Plugin installeret.', 'ansico-plugins')) . '
' . esc_html__('Tilbage til Ansico Plugins', 'ansico-plugins') . ' '; + + if ($plugin_file && function_exists('is_plugin_inactive') && is_plugin_inactive($plugin_file)) { + $activate_url = wp_nonce_url(admin_url('plugins.php?action=activate&plugin=' . rawurlencode($plugin_file)), 'activate-plugin_' . $plugin_file); + echo '' . esc_html__('Aktivér plugin', 'ansico-plugins') . ' '; + } + + echo '' . esc_html__('Gå til Plugins', 'ansico-plugins') . '
'; + echo ''; + + require_once ABSPATH . 'wp-admin/admin-footer.php'; + exit; + } + + public function delete_plugin($repo_name, $plugin_file) { + require_once ABSPATH . 'wp-admin/includes/file.php'; + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; + + if ('' === $plugin_file || !file_exists(WP_PLUGIN_DIR . '/' . ltrim($plugin_file, '/'))) { + wp_die(esc_html__('Pluginfil blev ikke fundet.', 'ansico-plugins')); + } + + if (!function_exists('deactivate_plugins')) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + if (!function_exists('get_current_screen')) { + require_once ABSPATH . 'wp-admin/includes/screen.php'; + } + + set_current_screen('plugins_page_ansico-plugins-catalog'); + require_once ABSPATH . 'wp-admin/admin-header.php'; + + echo '' . esc_html($message) . '
' . esc_html__('Tilbage til Ansico Plugins', 'ansico-plugins') . '
'; + echo '' . esc_html__('Plugin afinstalleret.', 'ansico-plugins') . '
' . esc_html__('Tilbage til Ansico Plugins', 'ansico-plugins') . '
'; + echo ''; + + require_once ABSPATH . 'wp-admin/admin-footer.php'; + exit; + } + + private function find_existing_plugin_file($owner, $repo_name, $slug) { + if (!function_exists('get_plugins')) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + $managed_plugins = Ansico_Plugins::get_managed_plugins(); + foreach ($managed_plugins as $plugin_file => $meta) { + if (!empty($meta['owner']) && !empty($meta['repo']) && $meta['owner'] === $owner && $meta['repo'] === $repo_name) { + return $plugin_file; + } + } + + $plugins = get_plugins(); + $slug = sanitize_title($slug); + foreach ($plugins as $plugin_file => $data) { + if (dirname($plugin_file) === $slug) { + return $plugin_file; + } + } + + return ''; + } + + public function inject_auth_header($args, $url) { + $settings = Ansico_Plugins::get_settings(); + $base = trailingslashit($settings['forgejo_base_url']); + + if (empty($settings['access_token']) || strpos($url, $base) !== 0) { + return $args; + } + + if (empty($args['headers']) || !is_array($args['headers'])) { + $args['headers'] = array(); + } + + $args['headers']['Authorization'] = 'token ' . trim((string) $settings['access_token']); + $args['sslverify'] = !empty($settings['verify_ssl']); + return $args; + } +} diff --git a/ansico-plugins/includes/class-ansico-plugins-updater.php b/ansico-plugins/includes/class-ansico-plugins-updater.php new file mode 100644 index 0000000..a98e463 --- /dev/null +++ b/ansico-plugins/includes/class-ansico-plugins-updater.php @@ -0,0 +1,307 @@ +checked) || !is_array($transient->checked)) { + return $transient; + } + + $managed_plugins = Ansico_Plugins::get_managed_plugins(); + if (empty($managed_plugins)) { + return $transient; + } + + if (!isset($transient->response) || !is_array($transient->response)) { + $transient->response = array(); + } + + if (!isset($transient->no_update) || !is_array($transient->no_update)) { + $transient->no_update = array(); + } + + $client = new Ansico_Plugins_Client(); + + foreach ($managed_plugins as $plugin_file => $meta) { + if (empty($transient->checked[$plugin_file])) { + continue; + } + + $owner = !empty($meta['owner']) ? $meta['owner'] : ''; + $repo = !empty($meta['repo']) ? $meta['repo'] : ''; + if ('' === $owner || '' === $repo) { + continue; + } + + $release = $client->get_latest_release($owner, $repo); + if (is_wp_error($release)) { + continue; + } + + $zip_asset = $client->find_zip_asset($release); + if (!$zip_asset || empty($zip_asset['browser_download_url'])) { + continue; + } + + $remote_version = $this->normalize_version($release); + $current_version = (string) $transient->checked[$plugin_file]; + $plugin_data = $this->get_plugin_header($plugin_file); + $slug = !empty($meta['slug']) ? $meta['slug'] : dirname($plugin_file); + + $item = (object) array( + 'id' => $plugin_file, + 'slug' => $slug, + 'plugin' => $plugin_file, + 'new_version' => $remote_version, + 'url' => !empty($meta['repo_html_url']) ? $meta['repo_html_url'] : '', + 'package' => $zip_asset['browser_download_url'], + 'icons' => array(), + 'banners' => array(), + 'banners_rtl' => array(), + 'tested' => !empty($plugin_data['RequiresWP']) ? $plugin_data['RequiresWP'] : '', + 'requires_php' => !empty($plugin_data['RequiresPHP']) ? $plugin_data['RequiresPHP'] : '', + 'compatibility' => new stdClass(), + ); + + if (version_compare($remote_version, $current_version, '>')) { + $transient->response[$plugin_file] = $item; + unset($transient->no_update[$plugin_file]); + } else { + $transient->no_update[$plugin_file] = $item; + unset($transient->response[$plugin_file]); + } + } + + $transient = $this->inject_self_update_data($transient, $client); + + return $transient; + } + + public function plugins_api($result, $action, $args) { + if ('plugin_information' !== $action || empty($args->slug)) { + return $result; + } + + $client = new Ansico_Plugins_Client(); + + if ($this->is_self_plugin_slug($args->slug)) { + $self_info = $this->get_self_update_info($client); + if (!$self_info) { + return $result; + } + + $sections = array( + 'description' => !empty($self_info['repo_meta']['plugin_headers']['Description']) ? wp_kses_post(wpautop($self_info['repo_meta']['plugin_headers']['Description'])) : wp_kses_post(wpautop(__('Ansico Plugins forbinder WordPress med private Forgejo-hostede plugins.', 'ansico-plugins'))), + 'installation' => wp_kses_post(wpautop(__('Installeres manuelt første gang. Derefter kan pluginet opdatere sig selv via Forgejo releases.', 'ansico-plugins'))), + 'changelog' => !empty($self_info['release']['body']) ? wp_kses_post(wpautop($self_info['release']['body'])) : wp_kses_post(wpautop(__('Ingen changelog tilgængelig.', 'ansico-plugins'))), + ); + + return (object) array( + 'name' => !empty($self_info['repo_meta']['readme_title']) ? $self_info['repo_meta']['readme_title'] : 'Ansico Plugins', + 'slug' => dirname(plugin_basename(ANSICO_PLUGINS_FILE)), + 'version' => $this->normalize_version($self_info['release']), + 'author' => !empty($self_info['repo_meta']['author_name']) ? '' . esc_html($self_info['repo_meta']['author_name']) . '' : 'Andreas Andersen (Ansico)', + 'author_profile' => !empty($self_info['repo_meta']['author_url']) ? $self_info['repo_meta']['author_url'] : 'https://ansico.dk', + 'homepage' => !empty($self_info['repo']['html_url']) ? $self_info['repo']['html_url'] : '', + 'requires' => !empty($self_info['plugin_data']['RequiresWP']) ? $self_info['plugin_data']['RequiresWP'] : '', + 'requires_php' => !empty($self_info['plugin_data']['RequiresPHP']) ? $self_info['plugin_data']['RequiresPHP'] : '', + 'tested' => get_bloginfo('version'), + 'last_updated' => !empty($self_info['release']['published_at']) ? gmdate('Y-m-d', strtotime($self_info['release']['published_at'])) : '', + 'download_link' => (!empty($self_info['zip_asset']['browser_download_url'])) ? $self_info['zip_asset']['browser_download_url'] : '', + 'sections' => $sections, + 'banners' => array(), + 'icons' => !empty($self_info['repo_meta']['screenshot_url']) ? array('default' => $self_info['repo_meta']['screenshot_url']) : array(), + 'external' => true, + ); + } + + $managed_plugins = Ansico_Plugins::get_managed_plugins(); + if (empty($managed_plugins)) { + return $result; + } + + $matched = null; + foreach ($managed_plugins as $plugin_file => $meta) { + if (!empty($meta['slug']) && $meta['slug'] === $args->slug) { + $matched = array('plugin_file' => $plugin_file, 'meta' => $meta); + break; + } + } + + if (!$matched) { + return $result; + } + + $release = $client->get_latest_release($matched['meta']['owner'], $matched['meta']['repo']); + if (is_wp_error($release)) { + return $result; + } + + $zip_asset = $client->find_zip_asset($release); + $plugin_data = $this->get_plugin_header($matched['plugin_file']); + $repo_meta = $client->get_repository_plugin_metadata($matched['meta']['owner'], $matched['meta']['repo']); + $repo_meta = is_wp_error($repo_meta) ? array() : $repo_meta; + $sections = array( + 'description' => !empty($matched['meta']['description']) ? wp_kses_post(wpautop($matched['meta']['description'])) : wp_kses_post(wpautop(__('Privat plugin distribueret via Forgejo.', 'ansico-plugins'))), + 'installation' => wp_kses_post(wpautop(__('Installeres og opdateres via Ansico Plugins og Forgejo releases.', 'ansico-plugins'))), + 'changelog' => !empty($release['body']) ? wp_kses_post(wpautop($release['body'])) : wp_kses_post(wpautop(__('Ingen changelog tilgængelig.', 'ansico-plugins'))), + ); + + return (object) array( + 'name' => !empty($plugin_data['Name']) ? $plugin_data['Name'] : $matched['meta']['repo'], + 'slug' => $matched['meta']['slug'], + 'version' => $this->normalize_version($release), + 'author' => !empty($repo_meta['author_name']) ? '' . esc_html($repo_meta['author_name']) . '' : 'Andreas Andersen (Ansico)', + 'author_profile' => !empty($repo_meta['author_url']) ? $repo_meta['author_url'] : 'https://ansico.dk', + 'homepage' => !empty($matched['meta']['repo_html_url']) ? $matched['meta']['repo_html_url'] : '', + 'requires' => !empty($plugin_data['RequiresWP']) ? $plugin_data['RequiresWP'] : '', + 'requires_php' => !empty($plugin_data['RequiresPHP']) ? $plugin_data['RequiresPHP'] : '', + 'tested' => get_bloginfo('version'), + 'last_updated' => !empty($release['published_at']) ? gmdate('Y-m-d', strtotime($release['published_at'])) : '', + 'download_link' => $zip_asset ? $zip_asset['browser_download_url'] : '', + 'sections' => $sections, + 'banners' => array(), + 'icons' => !empty($repo_meta['screenshot_url']) ? array('default' => $repo_meta['screenshot_url']) : array(), + 'external' => true, + ); + } + + + private function inject_self_update_data($transient, $client) { + $settings = Ansico_Plugins::get_settings(); + if (empty($settings['self_update_enabled']) || empty($settings['self_update_owner']) || empty($settings['self_update_repo'])) { + return $transient; + } + + $plugin_file = plugin_basename(ANSICO_PLUGINS_FILE); + if (empty($transient->checked[$plugin_file])) { + return $transient; + } + + $release = $client->get_latest_release($settings['self_update_owner'], $settings['self_update_repo']); + if (is_wp_error($release)) { + return $transient; + } + + $zip_asset = $client->find_zip_asset($release); + if (!$zip_asset || empty($zip_asset['browser_download_url'])) { + return $transient; + } + + $remote_version = $this->normalize_version($release); + $current_version = (string) $transient->checked[$plugin_file]; + $repo = $client->get_repository($settings['self_update_owner'], $settings['self_update_repo']); + $repo_url = (!is_wp_error($repo) && !empty($repo['html_url'])) ? (string) $repo['html_url'] : ''; + + $item = (object) array( + 'id' => $plugin_file, + 'slug' => dirname($plugin_file), + 'plugin' => $plugin_file, + 'new_version' => $remote_version, + 'url' => $repo_url, + 'package' => $zip_asset['browser_download_url'], + 'icons' => array(), + 'banners' => array(), + 'banners_rtl' => array(), + 'tested' => '', + 'requires_php' => '', + 'compatibility' => new stdClass(), + ); + + if (version_compare($remote_version, $current_version, '>')) { + $transient->response[$plugin_file] = $item; + unset($transient->no_update[$plugin_file]); + } else { + $transient->no_update[$plugin_file] = $item; + unset($transient->response[$plugin_file]); + } + + return $transient; + } + + private function is_self_plugin_slug($slug) { + return in_array((string) $slug, array('ansico-plugins', dirname(plugin_basename(ANSICO_PLUGINS_FILE))), true); + } + + private function get_self_update_info($client) { + $settings = Ansico_Plugins::get_settings(); + if (empty($settings['self_update_enabled']) || empty($settings['self_update_owner']) || empty($settings['self_update_repo'])) { + return null; + } + + $release = $client->get_latest_release($settings['self_update_owner'], $settings['self_update_repo']); + if (is_wp_error($release)) { + return null; + } + + $repo = $client->get_repository($settings['self_update_owner'], $settings['self_update_repo']); + $repo_meta = $client->get_repository_plugin_metadata($settings['self_update_owner'], $settings['self_update_repo']); + $zip_asset = $client->find_zip_asset($release); + $plugin_data = $this->get_plugin_header(plugin_basename(ANSICO_PLUGINS_FILE)); + + return array( + 'settings' => $settings, + 'release' => $release, + 'repo' => is_wp_error($repo) ? array() : $repo, + 'repo_meta' => is_wp_error($repo_meta) ? array() : $repo_meta, + 'zip_asset' => $zip_asset, + 'plugin_data' => $plugin_data, + ); + } + + public function inject_auth_header($args, $url) { + $settings = Ansico_Plugins::get_settings(); + $base = trailingslashit($settings['forgejo_base_url']); + + if (empty($settings['access_token']) || empty($base) || strpos($url, $base) !== 0) { + return $args; + } + + if (empty($args['headers']) || !is_array($args['headers'])) { + $args['headers'] = array(); + } + + $args['headers']['Authorization'] = 'token ' . trim((string) $settings['access_token']); + $args['sslverify'] = !empty($settings['verify_ssl']); + return $args; + } + + private function normalize_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 : '0.0.0'; + } + + private function get_plugin_header($plugin_file) { + if (!function_exists('get_plugin_data')) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + $path = WP_PLUGIN_DIR . '/' . ltrim($plugin_file, '/'); + if (!file_exists($path)) { + return array(); + } + + return get_plugin_data($path, false, false); + } +} diff --git a/ansico-plugins/readme.txt b/ansico-plugins/readme.txt new file mode 100644 index 0000000..a6acc28 --- /dev/null +++ b/ansico-plugins/readme.txt @@ -0,0 +1,56 @@ +=== Ansico Plugins === +Contributors: ansico +Tags: forgejo, plugins, private directory +Requires at least: 6.3 +Tested up to: 6.8 +Requires PHP: 7.4 +Stable tag: 0.0.0.3 +License: GPLv2 or later +License URI: https://www.gnu.org/licenses/gpl-2.0.html + +Privat plugin-directory til Forgejo. + +== Description == + +Ansico Plugins forbinder et WordPress-site til en Forgejo-server og viser udvalgte repositories som et privat plugin-katalog. + +Funktioner i version 0.0.0.2: +- Indstillingsside under Indstillinger → Ansico Plugins +- Test af Forgejo-forbindelse +- Filtrering af repositories via topic +- Oversigt over repos under Plugins → Ansico Plugins +- Installation/opdatering fra første ZIP-fil i seneste release +- Native update-check i WordPress for plugins installeret via Ansico Plugins +- Enkel plugin-information og changelog til WordPress' detaljevisning +- Enkel caching af Forgejo API-kald + +== Installation == + +1. Upload plugin-mappen til /wp-content/plugins/ eller installer ZIP-filen. +2. Aktivér pluginet i WordPress. +3. Gå til Indstillinger → Ansico Plugins. +4. Udfyld Forgejo base URL, owner/org og eventuelt access token. +5. Gem indstillinger og test forbindelsen. +6. Gå til Plugins → Ansico Plugins og installér fra seneste release. +7. Senere vil WordPress kunne opdage nye releases som opdateringer for plugins, der er installeret via Ansico Plugins. + +== Release workflow på Forgejo == + +1. Marker relevante repos med topic "wordpress-plugin" eller et andet topic efter eget valg. +2. Opret en release. +3. Upload en ZIP-fil som release-asset. +4. Sørg for at ZIP-filen er installérbar i WordPress og har plugin-mappen i roden. + +== Changelog == + += 0.0.0.2 = +* Tilføjet native opdateringskontrol i WordPress for plugins installeret via Ansico Plugins. +* Tilføjet plugin-information/changelog via plugins_api. +* Tilføjet enkel caching af Forgejo API-kald. + += 0.0.0.1 = +* Første MVP-version. + += 0.0.0.3 = +* Fixed organization repository topic filtering by fetching repo details when needed. +* Topic filter is now case-insensitive.