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.
## 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
Send users to:
+9 -1
View File
@@ -2,9 +2,11 @@
/**
* 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.
* Version: 2.1.0
* Version: 2.2.0
* Requires PHP: 7.4
* Update URI: https://github.com/dabestaniha/mahak-authenticate-bridge
* Author: Dabestaniha
*
* 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\SettingsPageMenuController;
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';
@@ -24,6 +30,8 @@ Autoloader::register([
require_once __DIR__.'/src/helpers.php';
PluginUpdater::boot();
add_action('init', fn() => AuthenticateController::resolve());
add_action('admin_menu', fn() => SettingsPageMenuController::resolve());
@@ -2,6 +2,8 @@
namespace Dabestaniha\AuthenticateBridge\App\Http\Controllers;
use Dabestaniha\AuthenticateBridge\App\Support\PluginUpdater;
class SettingsPageMenuController extends InvocableController
{
public function __invoke()
@@ -27,7 +29,8 @@ class SettingsPageMenuController extends InvocableController
submit_button();
?>
</form>
<?php PluginUpdater::renderUpdatePanel(); ?>
</div>
<?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');
}
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();
install_wordpress();
activate_and_configure_plugin();
@@ -285,5 +329,6 @@ wait_for_http();
test_valid_authentication();
test_invalid_authentication();
test_debug_error_details();
test_plugin_update_discovery();
echo "[OK] Authentication integration test passed\n";