feat: add GitHub-powered updates to WordPress admin
Build plugin release / release (push) Failing after 4s

This commit is contained in:
2026-08-24 09:27:21 +03:30
parent 572a740d46
commit 656fc907a9
6 changed files with 342 additions and 2 deletions
+38
View File
@@ -0,0 +1,38 @@
name: Build plugin release
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate version and build ZIP
env:
RELEASE_TAG: ${{ github.ref_name }}
run: |
VERSION="${RELEASE_TAG#v}"
grep -q "Version: ${VERSION}$" dabestaniha-authenticate-bridge.php
mkdir -p build/mahak-authenticate-bridge dist
rsync -a \
--exclude='.git/' \
--exclude='.github/' \
--exclude='build/' \
--exclude='dist/' \
--exclude='test/' \
./ build/mahak-authenticate-bridge/
cd build
zip -qr ../dist/mahak-authenticate-bridge.zip mahak-authenticate-bridge
- name: Publish GitHub release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ github.ref_name }}
run: gh release create "$RELEASE_TAG" dist/mahak-authenticate-bridge.zip --generate-notes --title "$RELEASE_TAG"
+6
View File
@@ -12,6 +12,12 @@ Authenticate WordPress user via json web token from Laravel Application.
Debug mode is disabled by default. An administrator can enable it from **Settings > Mahak Authentication** while troubleshooting. Because diagnostic pages expose server paths and other technical details to visitors of the login URL, disable it again after diagnosing the problem. Debug mode is disabled by default. An administrator can enable it from **Settings > Mahak Authentication** while troubleshooting. Because diagnostic pages expose server paths and other technical details to visitors of the login URL, disable it again after diagnosing the problem.
## Updates
Administrators can check for and install published releases from **Settings > Mahak Authentication**. Updates also appear in WordPress's standard **Plugins** and **Updates** screens.
To publish an update, change the plugin `Version` header and `MAHAK_AUTHENTICATE_BRIDGE_VERSION` constant to the same version, commit the change, and push a matching tag such as `v2.3.0` to GitHub. The release workflow builds and attaches the ZIP required by the WordPress updater.
## Usage ## Usage
Send users to: Send users to:
+9 -1
View File
@@ -2,9 +2,11 @@
/** /**
* Plugin Name: Mahak Authenticate Bridge * Plugin Name: Mahak Authenticate Bridge
* Plugin URI: https://github.com/dabestaniha/mahak-authenticate-bridge
* Description: Authenticate WordPress user via json web token from Mahakiha Application. * Description: Authenticate WordPress user via json web token from Mahakiha Application.
* Version: 2.1.0 * Version: 2.2.0
* Requires PHP: 7.4 * Requires PHP: 7.4
* Update URI: https://github.com/dabestaniha/mahak-authenticate-bridge
* Author: Dabestaniha * Author: Dabestaniha
* *
* Example: https://wordpress.test/mahak/login/?token=eyJ0... * Example: https://wordpress.test/mahak/login/?token=eyJ0...
@@ -14,6 +16,10 @@ use Dabestaniha\AuthenticateBridge\Autoloader;
use Dabestaniha\AuthenticateBridge\App\Http\Controllers\AuthenticateController; use Dabestaniha\AuthenticateBridge\App\Http\Controllers\AuthenticateController;
use Dabestaniha\AuthenticateBridge\App\Http\Controllers\SettingsPageMenuController; use Dabestaniha\AuthenticateBridge\App\Http\Controllers\SettingsPageMenuController;
use Dabestaniha\AuthenticateBridge\App\Http\Controllers\SettingsPageFormController; use Dabestaniha\AuthenticateBridge\App\Http\Controllers\SettingsPageFormController;
use Dabestaniha\AuthenticateBridge\App\Support\PluginUpdater;
define('MAHAK_AUTHENTICATE_BRIDGE_VERSION', '2.2.0');
define('MAHAK_AUTHENTICATE_BRIDGE_FILE', __FILE__);
require_once __DIR__.'/src/Autoloader.php'; require_once __DIR__.'/src/Autoloader.php';
@@ -24,6 +30,8 @@ Autoloader::register([
require_once __DIR__.'/src/helpers.php'; require_once __DIR__.'/src/helpers.php';
PluginUpdater::boot();
add_action('init', fn() => AuthenticateController::resolve()); add_action('init', fn() => AuthenticateController::resolve());
add_action('admin_menu', fn() => SettingsPageMenuController::resolve()); add_action('admin_menu', fn() => SettingsPageMenuController::resolve());
@@ -2,6 +2,8 @@
namespace Dabestaniha\AuthenticateBridge\App\Http\Controllers; namespace Dabestaniha\AuthenticateBridge\App\Http\Controllers;
use Dabestaniha\AuthenticateBridge\App\Support\PluginUpdater;
class SettingsPageMenuController extends InvocableController class SettingsPageMenuController extends InvocableController
{ {
public function __invoke() public function __invoke()
@@ -27,6 +29,7 @@ class SettingsPageMenuController extends InvocableController
submit_button(); submit_button();
?> ?>
</form> </form>
<?php PluginUpdater::renderUpdatePanel(); ?>
</div> </div>
<?php <?php
} }
+240
View File
@@ -0,0 +1,240 @@
<?php
namespace Dabestaniha\AuthenticateBridge\App\Support;
final class PluginUpdater
{
private const API_URL = 'https://api.github.com/repos/dabestaniha/mahak-authenticate-bridge/releases/latest';
private const RELEASE_ASSET = 'mahak-authenticate-bridge.zip';
private const RELEASE_CACHE = 'mahak_authenticate_bridge_release';
private const CHECK_ACTION = 'mahak_authenticate_check_updates';
private static ?self $instance = null;
private string $plugin;
private string $slug;
private function __construct()
{
$this->plugin = plugin_basename(MAHAK_AUTHENTICATE_BRIDGE_FILE);
$this->slug = dirname($this->plugin);
}
public static function boot(): void
{
if (self::$instance !== null) {
return;
}
self::$instance = new self();
add_filter('pre_set_site_transient_update_plugins', [self::$instance, 'addUpdate']);
add_filter('plugins_api', [self::$instance, 'addPluginInformation'], 20, 3);
add_action('admin_post_'.self::CHECK_ACTION, [self::$instance, 'checkNow']);
}
public function addUpdate($transient)
{
if (!is_object($transient) || empty($transient->checked)) {
return $transient;
}
$update = $this->availableUpdate();
if ($update !== null) {
if (!isset($transient->response) || !is_array($transient->response)) {
$transient->response = [];
}
$transient->response[$this->plugin] = $update;
}
return $transient;
}
public function addPluginInformation($result, string $action, $args)
{
if ($action !== 'plugin_information' || ($args->slug ?? '') !== $this->slug) {
return $result;
}
$release = $this->latestRelease();
if (is_wp_error($release)) {
return $result;
}
$package = $this->releasePackage($release);
if ($package === null) {
return $result;
}
return (object) [
'name' => 'Mahak Authenticate Bridge',
'slug' => $this->slug,
'version' => $this->releaseVersion($release),
'author' => 'Dabestaniha',
'homepage' => $release['html_url'] ?? 'https://github.com/dabestaniha/mahak-authenticate-bridge',
'download_link' => $package,
'requires_php' => '7.4',
'sections' => [
'description' => 'Authenticate WordPress users via JSON Web Tokens from the Mahakiha application.',
'changelog' => nl2br(esc_html((string) ($release['body'] ?? ''))),
],
];
}
public function checkNow(): void
{
if (!current_user_can('update_plugins')) {
wp_die(esc_html__('Sorry, you are not allowed to update plugins.'));
}
check_admin_referer(self::CHECK_ACTION);
delete_site_transient(self::RELEASE_CACHE);
delete_site_transient('update_plugins');
wp_update_plugins();
wp_safe_redirect(add_query_arg(
['page' => config('mahak.settings-page'), 'update-checked' => '1'],
admin_url('options-general.php')
));
exit;
}
public static function renderUpdatePanel(): void
{
if (self::$instance === null || !current_user_can('update_plugins')) {
return;
}
$updater = self::$instance;
$release = $updater->latestRelease();
echo '<hr><h2>'.esc_html__('Plugin updates').'</h2>';
echo '<p>'.sprintf(
esc_html__('Installed version: %s'),
'<strong>'.esc_html(MAHAK_AUTHENTICATE_BRIDGE_VERSION).'</strong>'
).'</p>';
if (is_wp_error($release)) {
echo '<div class="notice notice-warning inline"><p>'.esc_html($release->get_error_message()).'</p></div>';
} else {
$update = $updater->availableUpdate($release);
if ($update !== null) {
echo '<p>'.sprintf(esc_html__('Version %s is available.'), esc_html($update->new_version)).'</p>';
$updateUrl = wp_nonce_url(
self_admin_url('update.php?action=upgrade-plugin&plugin='.rawurlencode($updater->plugin)),
'upgrade-plugin_'.$updater->plugin
);
echo '<p><a class="button button-primary" href="'.esc_url($updateUrl).'">'.esc_html__('Update now').'</a></p>';
} else {
echo '<p>'.esc_html__('You are using the latest available version.').'</p>';
}
}
echo '<form method="post" action="'.esc_url(admin_url('admin-post.php')).'">';
echo '<input type="hidden" name="action" value="'.esc_attr(self::CHECK_ACTION).'">';
wp_nonce_field(self::CHECK_ACTION);
submit_button(esc_html__('Check for updates'), 'secondary', 'submit', false);
echo '</form>';
}
private function availableUpdate(?array $release = null): ?object
{
$release = $release ?? $this->latestRelease();
if (is_wp_error($release)) {
return null;
}
$version = $this->releaseVersion($release);
$package = $this->releasePackage($release);
if ($version === '' || $package === null || !version_compare($version, MAHAK_AUTHENTICATE_BRIDGE_VERSION, '>')) {
return null;
}
return (object) [
'id' => $release['html_url'] ?? self::API_URL,
'slug' => $this->slug,
'plugin' => $this->plugin,
'new_version' => $version,
'url' => $release['html_url'] ?? '',
'package' => $package,
'requires_php' => '7.4',
];
}
private function latestRelease()
{
$cached = get_site_transient(self::RELEASE_CACHE);
if (is_array($cached)) {
return $cached;
}
$response = wp_remote_get(self::API_URL, [
'headers' => [
'Accept' => 'application/vnd.github+json',
'User-Agent' => 'Mahak-Authenticate-Bridge/'.MAHAK_AUTHENTICATE_BRIDGE_VERSION,
],
'timeout' => 10,
]);
if (is_wp_error($response)) {
return new \WP_Error('mahak_update_request_failed', 'Could not contact GitHub to check for plugin updates.');
}
if (wp_remote_retrieve_response_code($response) !== 200) {
return new \WP_Error('mahak_update_response_invalid', 'No published plugin release is currently available on GitHub.');
}
$release = json_decode(wp_remote_retrieve_body($response), true);
if (!is_array($release) || !empty($release['draft']) || !empty($release['prerelease'])) {
return new \WP_Error('mahak_update_release_invalid', 'GitHub returned invalid plugin release information.');
}
if ($this->releaseVersion($release) === '' || $this->releasePackage($release) === null) {
return new \WP_Error(
'mahak_update_asset_missing',
'The latest GitHub release does not contain a valid mahak-authenticate-bridge.zip package.'
);
}
set_site_transient(self::RELEASE_CACHE, $release, 6 * HOUR_IN_SECONDS);
return $release;
}
private function releaseVersion(array $release): string
{
return ltrim((string) ($release['tag_name'] ?? ''), 'vV');
}
private function releasePackage(array $release): ?string
{
foreach (($release['assets'] ?? []) as $asset) {
if (($asset['name'] ?? '') !== self::RELEASE_ASSET) {
continue;
}
$url = esc_url_raw((string) ($asset['browser_download_url'] ?? ''));
$path = (string) wp_parse_url($url, PHP_URL_PATH);
if (
$url !== ''
&& wp_parse_url($url, PHP_URL_SCHEME) === 'https'
&& wp_parse_url($url, PHP_URL_HOST) === 'github.com'
&& strpos($path, '/dabestaniha/mahak-authenticate-bridge/releases/download/') === 0
) {
return $url;
}
}
return null;
}
}
+45
View File
@@ -278,6 +278,50 @@ function test_debug_error_details(): void
pass('Debug mode displays full exception details and stack trace'); pass('Debug mode displays full exception details and stack trace');
} }
function test_plugin_update_discovery(): void
{
$packageUrl = 'https://github.com/dabestaniha/mahak-authenticate-bridge/releases/download/v2.3.0/mahak-authenticate-bridge.zip';
$mockRelease = function ($response, array $request, string $url) use ($packageUrl) {
if ($url !== 'https://api.github.com/repos/dabestaniha/mahak-authenticate-bridge/releases/latest') {
return $response;
}
return [
'headers' => [],
'body' => json_encode([
'tag_name' => 'v2.3.0',
'html_url' => 'https://github.com/dabestaniha/mahak-authenticate-bridge/releases/tag/v2.3.0',
'body' => 'Test release',
'draft' => false,
'prerelease' => false,
'assets' => [[
'name' => 'mahak-authenticate-bridge.zip',
'browser_download_url' => $packageUrl,
]],
], JSON_THROW_ON_ERROR),
'response' => ['code' => 200, 'message' => 'OK'],
'cookies' => [],
'filename' => null,
];
};
delete_site_transient('mahak_authenticate_bridge_release');
add_filter('pre_http_request', $mockRelease, 10, 3);
$updates = apply_filters('pre_set_site_transient_update_plugins', (object) [
'checked' => [TEST_PLUGIN => MAHAK_AUTHENTICATE_BRIDGE_VERSION],
'response' => [],
]);
remove_filter('pre_http_request', $mockRelease, 10);
assert_true(isset($updates->response[TEST_PLUGIN]), 'A newer GitHub release was not offered as a WordPress update.');
assert_true($updates->response[TEST_PLUGIN]->new_version === '2.3.0', 'The offered plugin version was incorrect.');
assert_true($updates->response[TEST_PLUGIN]->package === $packageUrl, 'The release package URL was incorrect.');
pass('Published GitHub releases are discovered by the WordPress updater');
}
boot_wordpress(); boot_wordpress();
install_wordpress(); install_wordpress();
activate_and_configure_plugin(); activate_and_configure_plugin();
@@ -285,5 +329,6 @@ wait_for_http();
test_valid_authentication(); test_valid_authentication();
test_invalid_authentication(); test_invalid_authentication();
test_debug_error_details(); test_debug_error_details();
test_plugin_update_discovery();
echo "[OK] Authentication integration test passed\n"; echo "[OK] Authentication integration test passed\n";