diff --git a/README.md b/README.md index d06d00f..67efa89 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,38 @@ -# Ansico-plugins +# Ansico Plugins -WP plugin that makes it possible to install and update other Ansico plugins on your WP site. It connect to this Forgejo server. \ No newline at end of file +Ansico Plugins gives WordPress access to Ansico's private Forgejo-based plugin directory and lets administrators install, activate, reinstall, update and delete internal plugins from Ansico releases. + +## Installation + +1. Upload the plugin ZIP through WordPress, or copy the plugin folder to `/wp-content/plugins/`. +2. Activate **Ansico Plugins**. +3. Open **Settings → Ansico Plugins**. +4. Review the default Ansico settings or click **Reset til Ansico standard**. +5. Open **Plugins → Ansico Plugins** to browse available plugins. + +## Changelog + +### 1.0.0.2 + +- Added support for parsing `readme.txt` in WordPress format. +- Added tabbed plugin details view with Description, Installation, Changelog and Screenshots. +- Added generated plugin icon and plugin banner assets. +- Improved metadata extraction priority so `readme.txt` can drive plugin presentation. + +### 1.0.0 + +- First stable release. +- WordPress-style plugin catalogue UI. +- Details popup with metadata and optional screenshot. +- Install, activate, reinstall, update and delete actions. +- Self-update support for Ansico Plugins. +- Ansico default configuration and reset button. + +## Screenshots + +1. The Ansico Plugins catalogue in WordPress admin. +2. The plugin details modal with tabs and metadata. + +## Support + +Support URL: https://ansico.dk/Ansico/Ansico-plugins diff --git a/README.md:Zone.Identifier b/README.md:Zone.Identifier new file mode 100644 index 0000000..d6c1ec6 Binary files /dev/null and b/README.md:Zone.Identifier differ diff --git a/ansico-plugins-1.0.0.2.zip b/ansico-plugins-1.0.0.2.zip new file mode 100644 index 0000000..c6a385b Binary files /dev/null and b/ansico-plugins-1.0.0.2.zip differ diff --git a/ansico-plugins-1.0.0.2.zip:Zone.Identifier b/ansico-plugins-1.0.0.2.zip:Zone.Identifier new file mode 100644 index 0000000..d6c1ec6 Binary files /dev/null and b/ansico-plugins-1.0.0.2.zip:Zone.Identifier differ diff --git a/ansico-plugins/README.md b/ansico-plugins/README.md index 3e96f96..67efa89 100644 --- a/ansico-plugins/README.md +++ b/ansico-plugins/README.md @@ -1,45 +1,38 @@ # Ansico Plugins -Version: 1.0.0 -Contributor: aphandersen -Forfatter: Andreas Andersen (Ansico) -Author URL: https://ansico.dk -Support URL: https://ansico.dk/Ansico/Ansico-plugins -Licens: GPL-3.0-or-later -Testet på WordPress: 6.9.4 -Kræver PHP: 7.0 +Ansico Plugins gives WordPress access to Ansico's private Forgejo-based plugin directory and lets administrators install, activate, reinstall, update and delete internal plugins from Ansico releases. -## Beskrivelse +## Installation -Gives access to Ansico plugin directory with Ansico plugins. +1. Upload the plugin ZIP through WordPress, or copy the plugin folder to `/wp-content/plugins/`. +2. Activate **Ansico Plugins**. +3. Open **Settings → Ansico Plugins**. +4. Review the default Ansico settings or click **Reset til Ansico standard**. +5. Open **Plugins → Ansico Plugins** to browse available plugins. -## Formål +## Changelog -Dette plugin forbinder WordPress til Ansico's Forgejo-server og giver adgang til Ansico plugins gennem et privat plugin-katalog. +### 1.0.0.2 -## Funktioner +- Added support for parsing `readme.txt` in WordPress format. +- Added tabbed plugin details view with Description, Installation, Changelog and Screenshots. +- Added generated plugin icon and plugin banner assets. +- Improved metadata extraction priority so `readme.txt` can drive plugin presentation. -- Viser interne plugin-repositories fra Ansico -- Åbner en WordPress-lignende popup med plugin-detaljer -- Kan installere, aktivere, geninstallere, opdatere og afinstallere plugins -- Kan læse metadata fra repository og plugin-header -- Kan vise `screenshot.png`, hvis filen findes i repository-roden -- Kan opdatere Ansico Plugins selv via release-systemet -- Leveres med Ansico-standardindstillinger og reset-knap +### 1.0.0 -## Standardindstillinger +- First stable release. +- WordPress-style plugin catalogue UI. +- Details popup with metadata and optional screenshot. +- Install, activate, reinstall, update and delete actions. +- Self-update support for Ansico Plugins. +- Ansico default configuration and reset button. -Ved første installation er disse værdier udfyldt: +## Screenshots -- Forgejo URL: `https://ansico.dk` -- Bruger eller organisation: `Ansico` -- Access token: tom -- Topic-filter: `wordpress-plugin` -- Owner type: `Organisation` -- Selvopdatering: aktiv -- Ansico plugins owner: `Ansico` -- Ansico plugins repository: `Ansico-plugins` +1. The Ansico Plugins catalogue in WordPress admin. +2. The plugin details modal with tabs and metadata. -## Release +## Support -Denne version er klargjort som officiel release `1.0.0`. +Support URL: https://ansico.dk/Ansico/Ansico-plugins diff --git a/ansico-plugins/README.md:Zone.Identifier b/ansico-plugins/README.md:Zone.Identifier index 343fd8c..20f0320 100644 Binary files a/ansico-plugins/README.md:Zone.Identifier and b/ansico-plugins/README.md:Zone.Identifier differ diff --git a/ansico-plugins/ansico-plugins.php b/ansico-plugins/ansico-plugins.php index d8dc566..5031054 100644 --- a/ansico-plugins/ansico-plugins.php +++ b/ansico-plugins/ansico-plugins.php @@ -3,7 +3,7 @@ * Plugin Name: Ansico Plugins * Plugin URI: https://ansico.dk * Description: Gives access to Ansico plugin directory with Ansico plugins - * Version: 1.0.0 + * Version: 1.0.0.2 * Author: Andreas Andersen (Ansico) * Author URI: https://ansico.dk * Contributors: aphandersen @@ -20,7 +20,7 @@ if (!defined('ABSPATH')) { exit; } -define('ANSICO_PLUGINS_VERSION', '1.0.0'); +define('ANSICO_PLUGINS_VERSION', '1.0.0.2'); define('ANSICO_PLUGINS_MANAGED_OPTION', 'ansico_plugins_managed_plugins'); define('ANSICO_PLUGINS_FILE', __FILE__); define('ANSICO_PLUGINS_DIR', plugin_dir_path(__FILE__)); diff --git a/ansico-plugins/ansico-plugins.php:Zone.Identifier b/ansico-plugins/ansico-plugins.php:Zone.Identifier index 343fd8c..20f0320 100644 Binary files a/ansico-plugins/ansico-plugins.php:Zone.Identifier and b/ansico-plugins/ansico-plugins.php:Zone.Identifier differ diff --git a/ansico-plugins/assets/banner-1544x500.png b/ansico-plugins/assets/banner-1544x500.png new file mode 100644 index 0000000..d186c3c Binary files /dev/null and b/ansico-plugins/assets/banner-1544x500.png differ diff --git a/ansico-plugins/assets/banner-1544x500.png:Zone.Identifier b/ansico-plugins/assets/banner-1544x500.png:Zone.Identifier new file mode 100644 index 0000000..20f0320 Binary files /dev/null and b/ansico-plugins/assets/banner-1544x500.png:Zone.Identifier differ diff --git a/ansico-plugins/assets/banner-772x250.png b/ansico-plugins/assets/banner-772x250.png new file mode 100644 index 0000000..da66b37 Binary files /dev/null and b/ansico-plugins/assets/banner-772x250.png differ diff --git a/ansico-plugins/assets/banner-772x250.png:Zone.Identifier b/ansico-plugins/assets/banner-772x250.png:Zone.Identifier new file mode 100644 index 0000000..20f0320 Binary files /dev/null and b/ansico-plugins/assets/banner-772x250.png:Zone.Identifier differ diff --git a/ansico-plugins/assets/icon-128x128.png b/ansico-plugins/assets/icon-128x128.png new file mode 100644 index 0000000..0869304 Binary files /dev/null and b/ansico-plugins/assets/icon-128x128.png differ diff --git a/ansico-plugins/assets/icon-128x128.png:Zone.Identifier b/ansico-plugins/assets/icon-128x128.png:Zone.Identifier new file mode 100644 index 0000000..20f0320 Binary files /dev/null and b/ansico-plugins/assets/icon-128x128.png:Zone.Identifier differ diff --git a/ansico-plugins/assets/icon-256x256.png b/ansico-plugins/assets/icon-256x256.png new file mode 100644 index 0000000..10f8760 Binary files /dev/null and b/ansico-plugins/assets/icon-256x256.png differ diff --git a/ansico-plugins/assets/icon-256x256.png:Zone.Identifier b/ansico-plugins/assets/icon-256x256.png:Zone.Identifier new file mode 100644 index 0000000..20f0320 Binary files /dev/null and b/ansico-plugins/assets/icon-256x256.png:Zone.Identifier differ diff --git a/ansico-plugins/includes/class-ansico-plugins-admin.php b/ansico-plugins/includes/class-ansico-plugins-admin.php index 37b1faa..d36c4a1 100644 --- a/ansico-plugins/includes/class-ansico-plugins-admin.php +++ b/ansico-plugins/includes/class-ansico-plugins-admin.php @@ -333,13 +333,13 @@ class Ansico_Plugins_Admin { echo '
'; echo '
'; - echo '
AP
'; + echo $this->get_plugin_icon_markup($metadata, $display_name); echo '
'; echo '

' . esc_html($display_name) . '

'; echo '
' . $badge . '
'; echo '
'; - echo '

' . esc_html(!empty($repo['description']) ? $repo['description'] : __('Ingen beskrivelse angivet.', 'ansico-plugins')) . '

'; + echo '

' . esc_html(!empty($metadata['description']) ? $metadata['description'] : (!empty($repo['description']) ? $repo['description'] : __('Ingen beskrivelse angivet.', 'ansico-plugins'))) . '

'; echo '
'; echo '' . esc_html__('Installeret', 'ansico-plugins') . ': ' . esc_html($plugin_state['installed_version'] !== '' ? $plugin_state['installed_version'] : '—') . ''; @@ -563,12 +563,20 @@ class Ansico_Plugins_Admin { private function get_details_content_markup($repo, $release, $zip_asset, $plugin_state, $metadata) { $display_name = !empty($metadata['readme_title']) ? $metadata['readme_title'] : $repo['name']; + $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']); $html = ''; + + if (!empty($metadata['banner_url'])) { + $html .= '
' . esc_attr($display_name) . '
'; + } + $html .= '
'; - $html .= '
AP
'; + $html .= $this->get_plugin_icon_markup($metadata, $display_name); $html .= '
'; $html .= '

' . esc_html($display_name) . '

'; - $html .= '

' . esc_html(!empty($repo['description']) ? $repo['description'] : __('Ingen beskrivelse angivet.', 'ansico-plugins')) . '

'; + $html .= '

' . esc_html($description) . '

'; $html .= $this->get_state_badge($plugin_state); $html .= '
'; @@ -579,7 +587,6 @@ class Ansico_Plugins_Admin { $html .= '' . esc_html__('Åbn repository', 'ansico-plugins') . ''; $html .= '
'; - $html .= '
'; $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) : '—', @@ -592,8 +599,71 @@ class Ansico_Plugins_Admin { __('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'] : '—', ); + 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 .= '

' . esc_attr(sprintf(__('Screenshot for %s', 'ansico-plugins'), $display_name)) . '

'; + } + 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' => '
' . wp_kses_post(wpautop($release['body'])) . '
', + ); + } + + $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 '
' . esc_attr($display_name) . '
'; + } + return '
AP
'; + } + + private function render_metadata_grid($meta_rows) { + $html = '
'; foreach ($meta_rows as $label => $value) { $html .= '

' . esc_html($label) . '
'; if (is_string($value) && preg_match('#^https?://#i', $value)) { @@ -604,22 +674,59 @@ class Ansico_Plugins_Admin { $html .= '

'; } $html .= '
'; - - if (!empty($metadata['screenshot_url'])) { - $html .= '
'; - $html .= '

' . esc_html__('Screenshot', 'ansico-plugins') . '

'; - $html .= '' . esc_attr(sprintf(__('Screenshot for %s', 'ansico-plugins'), $display_name)) . ''; - $html .= '
'; - } - - if (!is_wp_error($release) && !empty($release['body'])) { - $html .= '

' . esc_html__('Release-noter', 'ansico-plugins') . '

'; - $html .= '
' . wp_kses_post(wpautop($release['body'])) . '
'; - } - return $html; } + private function render_details_tabs($tabs, $repo_name) { + $id_base = 'ansico-tabs-' . sanitize_title($repo_name); + $html = '
'; + foreach ($tabs as $index => $tab) { + $active = 0 === $index ? ' is-active' : ''; + $html .= ''; + } + $html .= '
'; + foreach ($tabs as $index => $tab) { + $active = 0 === $index ? ' is-active' : ''; + $html .= '
' . $tab['content'] . '
'; + } + $html .= '
'; + return $html; + } + + private function render_readme_section($content, $is_wordpress_readme = false) { + $content = trim((string) $content); + if ('' === $content) { + return '

' . esc_html__('Ingen oplysninger tilgængelige.', 'ansico-plugins') . '

'; + } + if ($is_wordpress_readme) { + return wp_kses_post(wpautop($this->format_wordpress_readme_text($content))); + } + return wp_kses_post(wpautop($this->format_markdown_text($content))); + } + + private function format_wordpress_readme_text($content) { + $content = preg_replace('/^=\s*(.+?)\s*=\s*$/m', " +$1 +", $content); + $content = preg_replace('/^\*\s+/m', '• ', $content); + return $content; + } + + private function format_markdown_text($content) { + $content = preg_replace('/^###\s+(.+)$/m', " +$1 +", $content); + $content = preg_replace('/^##\s+(.+)$/m', " +$1 +", $content); + $content = preg_replace('/^#\s+(.+)$/m', " +$1 +", $content); + $content = preg_replace('/^[-*]\s+/m', '• ', $content); + $content = preg_replace('/`([^`]+)`/', '$1', $content); + return $content; + } + private function render_catalog_styles() { echo ''; + '; } } diff --git a/ansico-plugins/includes/class-ansico-plugins-admin.php:Zone.Identifier b/ansico-plugins/includes/class-ansico-plugins-admin.php:Zone.Identifier index 343fd8c..20f0320 100644 Binary files a/ansico-plugins/includes/class-ansico-plugins-admin.php:Zone.Identifier and b/ansico-plugins/includes/class-ansico-plugins-admin.php:Zone.Identifier differ diff --git a/ansico-plugins/includes/class-ansico-plugins-client.php b/ansico-plugins/includes/class-ansico-plugins-client.php index 9f6d06d..cfe5f7a 100644 --- a/ansico-plugins/includes/class-ansico-plugins-client.php +++ b/ansico-plugins/includes/class-ansico-plugins-client.php @@ -303,23 +303,188 @@ class Ansico_Plugins_Client { public function get_repository_readme_title($owner, $repo_name, $ref = '') { - $candidates = array('README.md', 'readme.md', 'Readme.md'); + $documentation = $this->get_repository_documentation($owner, $repo_name, $ref); + if (is_wp_error($documentation)) { + return ''; + } + return !empty($documentation['title']) ? (string) $documentation['title'] : ''; + } + + public function get_repository_documentation($owner, $repo_name, $ref = '') { + $candidates = array('readme.txt', 'README.txt', 'README.md', 'readme.md', 'Readme.md'); foreach ($candidates as $candidate) { - $file = $this->get_repository_contents($owner, $repo_name, $candidate, $ref); - if (is_wp_error($file) || !is_array($file) || empty($file['content']) || empty($file['encoding']) || 'base64' !== $file['encoding']) { + $raw = $this->get_repository_text_file($owner, $repo_name, $candidate, $ref); + if ('' === $raw) { continue; } - $raw = base64_decode((string) $file['content'], true); - if (false === $raw) { - continue; + if (preg_match('/\.txt$/i', $candidate)) { + $parsed = $this->parse_wordpress_readme($raw); + } else { + $parsed = $this->parse_markdown_readme($raw); } - if (preg_match('/^\#\s+(.+)$/m', (string) $raw, $matches)) { - return trim(wp_strip_all_tags($matches[1])); + if (!empty($parsed['title']) || !empty($parsed['short_description']) || !empty($parsed['sections'])) { + $parsed['source'] = $candidate; + return $parsed; } } + return array( + 'title' => '', + 'short_description' => '', + 'sections' => array(), + 'source' => '', + ); + } + + private function get_repository_text_file($owner, $repo_name, $path, $ref = '') { + $file = $this->get_repository_contents($owner, $repo_name, $path, $ref); + if (is_wp_error($file) || !is_array($file) || empty($file['content']) || empty($file['encoding']) || 'base64' !== $file['encoding']) { + return ''; + } + + $raw = base64_decode((string) $file['content'], true); + if (false === $raw) { + return ''; + } + + return str_replace(array(" +", " "), " +", (string) $raw); + } + + private function parse_wordpress_readme($raw) { + $raw = trim((string) $raw); + $lines = preg_split('/ +/', $raw); + $title = ''; + $short_description = ''; + $sections = array(); + $current = ''; + $buffer = array(); + $header_done = false; + + foreach ($lines as $line) { + if ('' === $title && preg_match('/^===\s*(.+?)\s*===\s*$/', $line, $m)) { + $title = trim($m[1]); + continue; + } + + if (!$header_done && preg_match('/^==\s*(.+?)\s*==\s*$/', $line, $m)) { + $header_done = true; + $current = strtolower(trim($m[1])); + $buffer = array(); + continue; + } + + if (!$header_done) { + if ('' === $short_description && '' !== trim($line) && false === strpos($line, ':')) { + $short_description = trim($line); + } + continue; + } + + if (preg_match('/^==\s*(.+?)\s*==\s*$/', $line, $m)) { + if ('' !== $current) { + $sections[$current] = trim(implode(" +", $buffer)); + } + $current = strtolower(trim($m[1])); + $buffer = array(); + continue; + } + + $buffer[] = $line; + } + + if ('' !== $current) { + $sections[$current] = trim(implode(" +", $buffer)); + } + + if ('' === $short_description && !empty($sections['description'])) { + $short_description = $this->first_paragraph($sections['description']); + } + + return array( + 'title' => $title, + 'short_description' => $short_description, + 'sections' => $sections, + ); + } + + private function parse_markdown_readme($raw) { + $title = ''; + $short_description = ''; + $sections = array(); + $current = ''; + $buffer = array(); + $after_title = ''; + + foreach (preg_split('/ +/', (string) $raw) as $line) { + if ('' === $title && preg_match('/^#\s+(.+)$/', $line, $m)) { + $title = trim(wp_strip_all_tags($m[1])); + continue; + } + + if (preg_match('/^##\s+(.+)$/', $line, $m)) { + if ('' !== $current) { + $sections[$current] = trim(implode(" +", $buffer)); + } + $current = strtolower(trim(wp_strip_all_tags($m[1]))); + $buffer = array(); + continue; + } + + if ('' === $current) { + $after_title .= $line . " +"; + } else { + $buffer[] = $line; + } + } + + if ('' !== $current) { + $sections[$current] = trim(implode(" +", $buffer)); + } + + $short_description = $this->first_paragraph($after_title); + if ('' === $short_description && !empty($sections['description'])) { + $short_description = $this->first_paragraph($sections['description']); + } + + return array( + 'title' => $title, + 'short_description' => $short_description, + 'sections' => $sections, + ); + } + + private function first_paragraph($text) { + $text = trim((string) $text); + if ('' === $text) { + return ''; + } + $parts = preg_split("/ +\s* +/", $text); + if (empty($parts[0])) { + return ''; + } + return trim(wp_strip_all_tags($parts[0])); + } + + private function find_repository_asset_url($owner, $repo_name, $default_branch, $candidates) { + foreach ((array) $candidates as $candidate) { + $file = $this->get_repository_contents($owner, $repo_name, $candidate, $default_branch); + if (!is_wp_error($file) && is_array($file) && !empty($file['download_url'])) { + return (string) $file['download_url']; + } + } return ''; } @@ -331,54 +496,53 @@ class Ansico_Plugins_Client { $default_branch = !empty($repo['default_branch']) ? (string) $repo['default_branch'] : ''; $root = $this->get_repository_contents($owner, $repo_name, '', $default_branch); - if (is_wp_error($root) || !is_array($root)) { - return array( - 'readme_title' => '', - 'author_name' => !empty($repo['owner']['full_name']) ? (string) $repo['owner']['full_name'] : (!empty($repo['owner']['login']) ? (string) $repo['owner']['login'] : ''), - 'author_url' => !empty($repo['owner']['website']) ? (string) $repo['owner']['website'] : (!empty($repo['owner']['html_url']) ? (string) $repo['owner']['html_url'] : ''), - 'plugin_headers' => array(), - 'support_url' => !empty($repo['html_url']) ? trailingslashit((string) $repo['html_url']) . 'issues' : '', - 'license' => !empty($repo['license']['spdx_id']) ? (string) $repo['license']['spdx_id'] : '', - 'screenshot_url' => '', - 'default_branch' => $default_branch, - ); - } - - $screenshot_url = ''; $plugin_headers = array(); - foreach ($root as $item) { - if (!is_array($item) || empty($item['name'])) { - continue; - } - $name = (string) $item['name']; - if (strtolower($name) === 'screenshot.png' && !empty($item['download_url'])) { - $screenshot_url = (string) $item['download_url']; - } - if (empty($plugin_headers) && !empty($item['type']) && 'file' === $item['type'] && preg_match('/\.php$/i', $name)) { - $file = $this->get_repository_contents($owner, $repo_name, $name, $default_branch); - if (!is_wp_error($file) && is_array($file) && !empty($file['content']) && !empty($file['encoding']) && 'base64' === $file['encoding']) { - $raw = base64_decode((string) $file['content'], true); - if (false !== $raw) { - $plugin_headers = $this->parse_plugin_headers($raw); + + if (!is_wp_error($root) && is_array($root)) { + foreach ($root as $item) { + if (!is_array($item) || empty($item['name'])) { + continue; + } + $name = (string) $item['name']; + if (empty($plugin_headers) && !empty($item['type']) && 'file' === $item['type'] && preg_match('/\.php$/i', $name)) { + $file = $this->get_repository_contents($owner, $repo_name, $name, $default_branch); + if (!is_wp_error($file) && is_array($file) && !empty($file['content']) && !empty($file['encoding']) && 'base64' === $file['encoding']) { + $raw = base64_decode((string) $file['content'], true); + if (false !== $raw) { + $plugin_headers = $this->parse_plugin_headers($raw); + } } } } } - $readme_title = $this->get_repository_readme_title($owner, $repo_name, $default_branch); + $documentation = $this->get_repository_documentation($owner, $repo_name, $default_branch); + if (is_wp_error($documentation)) { + $documentation = array('title' => '', 'short_description' => '', 'sections' => array(), 'source' => ''); + } + $author_name = !empty($plugin_headers['Author']) ? (string) $plugin_headers['Author'] : (!empty($repo['owner']['full_name']) ? (string) $repo['owner']['full_name'] : (!empty($repo['owner']['login']) ? (string) $repo['owner']['login'] : '')); $author_url = !empty($plugin_headers['AuthorURI']) ? (string) $plugin_headers['AuthorURI'] : (!empty($repo['owner']['website']) ? (string) $repo['owner']['website'] : (!empty($repo['owner']['html_url']) ? (string) $repo['owner']['html_url'] : '')); $support_url = !empty($plugin_headers['SupportURI']) ? (string) $plugin_headers['SupportURI'] : (!empty($repo['html_url']) ? trailingslashit((string) $repo['html_url']) . 'issues' : ''); $license = !empty($plugin_headers['License']) ? (string) $plugin_headers['License'] : (!empty($repo['license']['spdx_id']) ? (string) $repo['license']['spdx_id'] : ''); + $screenshot_url = $this->find_repository_asset_url($owner, $repo_name, $default_branch, array('screenshot.png', 'assets/screenshot.png')); + $icon_url = $this->find_repository_asset_url($owner, $repo_name, $default_branch, array('icon-256x256.png', 'icon-128x128.png', 'assets/icon-256x256.png', 'assets/icon-128x128.png')); + $banner_url = $this->find_repository_asset_url($owner, $repo_name, $default_branch, array('banner-1544x500.png', 'banner-772x250.png', 'assets/banner-1544x500.png', 'assets/banner-772x250.png')); + return array( - 'readme_title' => $readme_title, + 'readme_title' => !empty($documentation['title']) ? $documentation['title'] : '', + 'description' => !empty($documentation['short_description']) ? $documentation['short_description'] : (!empty($repo['description']) ? (string) $repo['description'] : ''), + 'readme_sections' => !empty($documentation['sections']) && is_array($documentation['sections']) ? $documentation['sections'] : array(), + 'readme_source' => !empty($documentation['source']) ? $documentation['source'] : '', 'author_name' => $author_name, 'author_url' => $author_url, 'plugin_headers' => $plugin_headers, 'support_url' => $support_url, 'license' => $license, 'screenshot_url' => $screenshot_url, + 'icon_url' => $icon_url, + 'banner_url' => $banner_url, 'default_branch' => $default_branch, ); } diff --git a/ansico-plugins/includes/class-ansico-plugins-client.php:Zone.Identifier b/ansico-plugins/includes/class-ansico-plugins-client.php:Zone.Identifier index 343fd8c..20f0320 100644 Binary files a/ansico-plugins/includes/class-ansico-plugins-client.php:Zone.Identifier and b/ansico-plugins/includes/class-ansico-plugins-client.php:Zone.Identifier differ diff --git a/ansico-plugins/includes/class-ansico-plugins-installer.php:Zone.Identifier b/ansico-plugins/includes/class-ansico-plugins-installer.php:Zone.Identifier index 343fd8c..20f0320 100644 Binary files a/ansico-plugins/includes/class-ansico-plugins-installer.php:Zone.Identifier and b/ansico-plugins/includes/class-ansico-plugins-installer.php:Zone.Identifier differ diff --git a/ansico-plugins/includes/class-ansico-plugins-updater.php:Zone.Identifier b/ansico-plugins/includes/class-ansico-plugins-updater.php:Zone.Identifier index 343fd8c..20f0320 100644 Binary files a/ansico-plugins/includes/class-ansico-plugins-updater.php:Zone.Identifier and b/ansico-plugins/includes/class-ansico-plugins-updater.php:Zone.Identifier differ diff --git a/ansico-plugins/readme.txt b/ansico-plugins/readme.txt index 94e28ad..045b82a 100644 --- a/ansico-plugins/readme.txt +++ b/ansico-plugins/readme.txt @@ -4,7 +4,7 @@ Tags: forgejo, plugins, private directory, ansico Requires at least: 6.3 Tested up to: 6.9.4 Requires PHP: 7.0 -Stable tag: 1.0.0 +Stable tag: 1.0.0.2 License: GPL-3.0-or-later License URI: https://www.gnu.org/licenses/gpl-3.0.html @@ -12,7 +12,7 @@ Gives access to Ansico plugin directory with Ansico plugins. == Description == -Ansico Plugins gives WordPress access to Ansico's private Forgejo-based plugin directory and lets administrators install, activate, reinstall, update, and delete internal plugins from Ansico releases. +Ansico Plugins gives WordPress access to Ansico's private Forgejo-based plugin directory and lets administrators install, activate, reinstall, update and delete internal plugins from Ansico releases. = Features = @@ -21,9 +21,10 @@ Ansico Plugins gives WordPress access to Ansico's private Forgejo-based plugin d * Reads plugin metadata and README content from repositories. * Opens a WordPress-style details popup for each plugin. * Installs plugins from the latest Forgejo release ZIP. -* Supports activate, reinstall, update, and delete actions. +* Supports activate, reinstall, update and delete actions. * Supports self-updates for Ansico Plugins itself. * Includes Ansico default settings and a reset button. +* Supports plugin presentation from `readme.txt` in WordPress format. == Installation == @@ -43,12 +44,23 @@ Yes. Public Ansico repositories can be listed without a token. A token can still Yes. Self-update support is built in and can check the configured Ansico Plugins repository for newer releases. +== Screenshots == + +1. The Ansico Plugins catalogue in WordPress admin. +2. The plugin details modal with tabs and metadata. + == Changelog == += 1.0.0.2 = +* Added support for parsing `readme.txt` in WordPress format. +* Added tabbed plugin details view with Description, Installation, Changelog and Screenshots. +* Added generated plugin icon and plugin banner assets. +* Improved metadata extraction priority so `readme.txt` can drive plugin presentation. + = 1.0.0 = * First stable release. * WordPress-style plugin catalogue UI. * Details popup with metadata and optional screenshot. -* Install, activate, reinstall, update, and delete actions. +* Install, activate, reinstall, update and delete actions. * Self-update support for Ansico Plugins. * Ansico default configuration and reset button. diff --git a/ansico-plugins/readme.txt:Zone.Identifier b/ansico-plugins/readme.txt:Zone.Identifier index 343fd8c..20f0320 100644 Binary files a/ansico-plugins/readme.txt:Zone.Identifier and b/ansico-plugins/readme.txt:Zone.Identifier differ diff --git a/banner-1544x500.png b/banner-1544x500.png new file mode 100644 index 0000000..d186c3c Binary files /dev/null and b/banner-1544x500.png differ diff --git a/banner-1544x500.png:Zone.Identifier b/banner-1544x500.png:Zone.Identifier new file mode 100644 index 0000000..d6c1ec6 Binary files /dev/null and b/banner-1544x500.png:Zone.Identifier differ diff --git a/icon-256x256.png b/icon-256x256.png new file mode 100644 index 0000000..10f8760 Binary files /dev/null and b/icon-256x256.png differ diff --git a/icon-256x256.png:Zone.Identifier b/icon-256x256.png:Zone.Identifier new file mode 100644 index 0000000..d6c1ec6 Binary files /dev/null and b/icon-256x256.png:Zone.Identifier differ