Skip to content

Commit f99ea78

Browse files
authored
Restore pre-4.7.0 global class names as backward-compat aliases (#320)
* Restore pre-4.7.0 global class names as backward-compat aliases The 4.7.0 namespace refactoring removed all global classes, which broke third-party plugins that check for them — most notably Micropub, which gates its whole initialization (including its REST route registration) on class_exists( 'IndieAuth_Plugin' ). Register the old names as aliases of their namespaced replacements: IndieAuth_Plugin eagerly, so checks with autoloading disabled also work, and the rest lazily via spl_autoload_register. Fixes #319 Claude-Session: https://claude.ai/code/session_0126gxnmr2SMc4X4J1Y6yf71 * Add deprecation notices for legacy class names and require WP 6.4 Lazily aliased legacy classes now trigger _deprecated_class() on first use. IndieAuth_Plugin stays eagerly aliased and notice-free, because plugins like Micropub use it for feature detection. _deprecated_class() requires WordPress 6.4, so the minimum required version is raised from 6.2 to 6.4. Claude-Session: https://claude.ai/code/session_0126gxnmr2SMc4X4J1Y6yf71 * Keep WordPress 6.2 support: restore the _deprecated_class() guard The WP 6.4 requirement bump was meant for the WebFinger plugin, not for IndieAuth. Claude-Session: https://claude.ai/code/session_0126gxnmr2SMc4X4J1Y6yf71 * Only expect deprecation notices where _deprecated_class() exists ClassicPress does not have _deprecated_class(), so the compat autoloader's function_exists() guard correctly skips the notice there. Mirror that condition in the test instead of expecting the notice unconditionally, which failed the ClassicPress CI matrix. Claude-Session: https://claude.ai/code/session_0126gxnmr2SMc4X4J1Y6yf71 * Address review: guard eager alias, assert map/test consistency Guard the eager IndieAuth_Plugin alias against the name being already defined (e.g. by a site-level workaround for the 4.7.0 breakage). The lazy autoloader path needs no guard, because PHP never invokes autoloaders for defined classes. Add an assertion that COMPAT_CLASS_MAP and the test data provider cover the same classes, while keeping the provider as an independent pin of the pre-4.7.0 surface. Claude-Session: https://claude.ai/code/session_0126gxnmr2SMc4X4J1Y6yf71 * Bump version to 4.7.1 Claude-Session: https://claude.ai/code/session_0126gxnmr2SMc4X4J1Y6yf71 * Remove redundant test duplicating the eager alias assertion * Exclude incompatible endpoint aliases
1 parent a379dd0 commit f99ea78

4 files changed

Lines changed: 201 additions & 3 deletions

File tree

includes/compat.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
/**
3+
* Backward compatibility aliases for pre-4.7.0 class names.
4+
*
5+
* Version 4.7.0 moved all classes into the `IndieAuth` namespace. Third-party
6+
* plugins (e.g. Micropub) check for old global class names, so compatible
7+
* replacements are kept available as aliases.
8+
*
9+
* @package IndieAuth
10+
*/
11+
12+
namespace IndieAuth;
13+
14+
/**
15+
* Map of pre-4.7.0 global class names to their namespaced replacements.
16+
*
17+
* `IndieAuth_Endpoint` cannot be aliased because its functionality moved to a
18+
* trait. The authorization, introspection, metadata, revocation, ticket, token,
19+
* and userinfo endpoint classes are also excluded: their legacy `get_endpoint()`
20+
* methods were static, while the replacement controller methods are not.
21+
*/
22+
const COMPAT_CLASS_MAP = array(
23+
'External_Token_Page' => Ticket\External_Token_Page::class,
24+
'External_Token_Table' => WP_Admin\External_Token_List_Table::class,
25+
'External_User_Token' => Ticket\External_User_Token::class,
26+
'IndieAuth_Admin' => WP_Admin\Admin::class,
27+
'IndieAuth_Authorize' => Authorize::class,
28+
'IndieAuth_Client' => Client::class,
29+
'IndieAuth_Client_Discovery' => Client_Discovery::class,
30+
'IndieAuth_Client_Taxonomy' => Client_Taxonomy::class,
31+
'IndieAuth_Debug' => Debug::class,
32+
'IndieAuth_FedCM_Endpoint' => Rest\FedCM_Controller::class,
33+
'IndieAuth_Plugin' => IndieAuth::class,
34+
'IndieAuth_Scope' => Scope\Scope::class,
35+
'IndieAuth_Scopes' => Scopes::class,
36+
'IndieAuth_Token_UI' => WP_Admin\Token_UI::class,
37+
'IndieAuth_WebIdentity' => WebIdentity::class,
38+
'Token_Generic' => Token\Generic::class,
39+
'Token_List_Table' => WP_Admin\Token_List_Table::class,
40+
'Token_Transient' => Token\Transient::class,
41+
'Token_User' => Token\User::class,
42+
'WP_OAuth_Response' => OAuth_Response::class,
43+
'Web_Signin' => Web_Signin::class,
44+
);
45+
46+
\spl_autoload_register(
47+
function ( $class_name ) {
48+
if ( ! isset( COMPAT_CLASS_MAP[ $class_name ] ) ) {
49+
return;
50+
}
51+
52+
// `_deprecated_class()` requires WordPress 6.4.
53+
if ( \function_exists( '_deprecated_class' ) ) {
54+
\_deprecated_class( $class_name, '4.7.0', COMPAT_CLASS_MAP[ $class_name ] );
55+
}
56+
57+
\class_alias( COMPAT_CLASS_MAP[ $class_name ], $class_name );
58+
}
59+
);
60+
61+
/*
62+
* Micropub gates its whole initialization on `class_exists( 'IndieAuth_Plugin' )`,
63+
* and other plugins may check with autoloading disabled, so this alias is
64+
* registered eagerly instead of through the autoloader above. Guarded, because
65+
* sites may have defined the class themselves as a workaround for the 4.7.0
66+
* breakage, and `class_alias()` warns when the name is already taken.
67+
*/
68+
if ( ! \class_exists( 'IndieAuth_Plugin', false ) ) {
69+
\class_alias( IndieAuth::class, 'IndieAuth_Plugin' );
70+
}

indieauth.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Plugin Name: IndieAuth
44
* Plugin URI: https://github.com/indieweb/wordpress-indieauth/
55
* Description: IndieAuth is a way to allow users to use their own domain to sign into other websites and services
6-
* Version: 4.7.0
6+
* Version: 4.7.1
77
* Requires at least: 6.2
88
* Requires PHP: 7.4
99
* Requires CP: 2.1
@@ -19,7 +19,7 @@
1919

2020
namespace IndieAuth;
2121

22-
\define( 'INDIEAUTH_PLUGIN_VERSION', '4.7.0' );
22+
\define( 'INDIEAUTH_PLUGIN_VERSION', '4.7.1' );
2323
\define( 'INDIEAUTH_PLUGIN_DIR', \plugin_dir_path( __FILE__ ) );
2424
\define( 'INDIEAUTH_PLUGIN_BASENAME', \plugin_basename( __FILE__ ) );
2525
\define( 'INDIEAUTH_PLUGIN_FILE', INDIEAUTH_PLUGIN_DIR . \basename( __FILE__ ) );
@@ -36,6 +36,9 @@
3636

3737
Autoloader::register_path( __NAMESPACE__, __DIR__ . '/includes' );
3838

39+
// Backward compatibility aliases for pre-4.7.0 class names.
40+
require_once __DIR__ . '/includes/compat.php';
41+
3942
// Initialize the plugin.
4043
$indieauth = IndieAuth::get_instance();
4144
$indieauth->init();

readme.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
- Donate link: https://opencollective.com/indieweb
55
- Tags: IndieAuth, IndieWeb, login, oauth
66
- Tested up to: 7.0
7-
- Stable tag: 4.7.0
7+
- Stable tag: 4.7.1
88
- Requires PHP: 7.4
99
- License: MIT
1010
- License URI: https://opensource.org/licenses/MIT
@@ -189,6 +189,11 @@ In version 2.0, we added an IndieAuth endpoint to this plugin, which previously
189189

190190
Project and support maintained on github at [indieweb/wordpress-indieauth](https://github.com/indieweb/wordpress-indieauth).
191191

192+
### 4.7.1
193+
194+
* Restore compatible pre-4.7.0 global class names as aliases of their namespaced replacements, fixing plugins like Micropub that check for `IndieAuth_Plugin` before initializing (#319). The abstract `IndieAuth_Endpoint` and endpoint classes whose legacy static API is not preserved are intentionally excluded.
195+
* Trigger a deprecation notice when an aliased legacy class name is used, except for `IndieAuth_Plugin`, which plugins use for feature detection
196+
192197
### 4.7.0
193198

194199
* Fix fatal error on the authorization form when the current user cannot be determined
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
/**
3+
* Tests for the backward-compatibility class aliases for pre-4.7.0 class names.
4+
*
5+
* @package IndieAuth
6+
*/
7+
class Test_Compat extends WP_UnitTestCase {
8+
9+
/**
10+
* The IndieAuth_Plugin alias must exist without triggering the autoloader,
11+
* so that `class_exists( 'IndieAuth_Plugin', false )` checks also work.
12+
* The Micropub plugin gates its whole initialization on this class.
13+
*
14+
* @see https://github.com/indieweb/wordpress-indieauth/issues/319
15+
*/
16+
public function test_indieauth_plugin_alias_is_registered_eagerly() {
17+
$this->assertTrue( class_exists( 'IndieAuth_Plugin', false ) );
18+
}
19+
20+
/**
21+
* Map of pre-4.7.0 global class names to their namespaced replacements.
22+
*
23+
* @return array
24+
*/
25+
public function legacy_class_provider() {
26+
return array(
27+
array( 'External_Token_Page', \IndieAuth\Ticket\External_Token_Page::class ),
28+
array( 'External_Token_Table', \IndieAuth\WP_Admin\External_Token_List_Table::class ),
29+
array( 'External_User_Token', \IndieAuth\Ticket\External_User_Token::class ),
30+
array( 'IndieAuth_Admin', \IndieAuth\WP_Admin\Admin::class ),
31+
array( 'IndieAuth_Authorize', \IndieAuth\Authorize::class ),
32+
array( 'IndieAuth_Client', \IndieAuth\Client::class ),
33+
array( 'IndieAuth_Client_Discovery', \IndieAuth\Client_Discovery::class ),
34+
array( 'IndieAuth_Client_Taxonomy', \IndieAuth\Client_Taxonomy::class ),
35+
array( 'IndieAuth_Debug', \IndieAuth\Debug::class ),
36+
array( 'IndieAuth_FedCM_Endpoint', \IndieAuth\Rest\FedCM_Controller::class ),
37+
array( 'IndieAuth_Plugin', \IndieAuth\IndieAuth::class ),
38+
array( 'IndieAuth_Scope', \IndieAuth\Scope\Scope::class ),
39+
array( 'IndieAuth_Scopes', \IndieAuth\Scopes::class ),
40+
array( 'IndieAuth_Token_UI', \IndieAuth\WP_Admin\Token_UI::class ),
41+
array( 'IndieAuth_WebIdentity', \IndieAuth\WebIdentity::class ),
42+
array( 'Token_Generic', \IndieAuth\Token\Generic::class ),
43+
array( 'Token_List_Table', \IndieAuth\WP_Admin\Token_List_Table::class ),
44+
array( 'Token_Transient', \IndieAuth\Token\Transient::class ),
45+
array( 'Token_User', \IndieAuth\Token\User::class ),
46+
array( 'WP_OAuth_Response', \IndieAuth\OAuth_Response::class ),
47+
array( 'Web_Signin', \IndieAuth\Web_Signin::class ),
48+
);
49+
}
50+
51+
/**
52+
* Every legacy class name must resolve, via autoloading, to its namespaced replacement.
53+
*
54+
* @dataProvider legacy_class_provider
55+
*
56+
* @param string $legacy Pre-4.7.0 global class name.
57+
* @param string $current Namespaced replacement class.
58+
*/
59+
public function test_legacy_class_alias( $legacy, $current ) {
60+
/*
61+
* Lazily aliased classes must trigger a deprecation notice on first use.
62+
* `IndieAuth_Plugin` is aliased eagerly and stays notice-free, because
63+
* plugins like Micropub use it for feature detection. ClassicPress does
64+
* not have `_deprecated_class()`, so no notice is triggered there.
65+
*/
66+
if ( ! class_exists( $legacy, false ) && function_exists( '_deprecated_class' ) ) {
67+
$this->setExpectedDeprecated( $legacy );
68+
}
69+
70+
$this->assertTrue( class_exists( $legacy ), "Legacy class {$legacy} does not exist." );
71+
72+
$reflection = new ReflectionClass( $legacy );
73+
$this->assertSame( $current, $reflection->getName(), "Legacy class {$legacy} is not an alias of {$current}." );
74+
}
75+
76+
/**
77+
* The data provider intentionally duplicates the map as an independent pin
78+
* of the pre-4.7.0 public surface: removing an alias from production must
79+
* fail here. This assertion covers the other direction, so an alias added
80+
* to production cannot silently miss test coverage.
81+
*/
82+
public function test_compat_map_matches_legacy_class_provider() {
83+
$expected = array();
84+
foreach ( $this->legacy_class_provider() as $row ) {
85+
$expected[ $row[0] ] = $row[1];
86+
}
87+
88+
$this->assertSame( $expected, \IndieAuth\COMPAT_CLASS_MAP );
89+
}
90+
91+
/**
92+
* Endpoint classes whose legacy static API is not preserved by the new
93+
* controllers must not be exposed as misleading aliases.
94+
*
95+
* @return array
96+
*/
97+
public function incompatible_endpoint_class_provider() {
98+
return array(
99+
array( 'IndieAuth_Authorization_Endpoint' ),
100+
array( 'IndieAuth_Introspection_Endpoint' ),
101+
array( 'IndieAuth_Metadata_Endpoint' ),
102+
array( 'IndieAuth_Revocation_Endpoint' ),
103+
array( 'IndieAuth_Ticket_Endpoint' ),
104+
array( 'IndieAuth_Token_Endpoint' ),
105+
array( 'IndieAuth_Userinfo_Endpoint' ),
106+
);
107+
}
108+
109+
/**
110+
* Ensure incompatible endpoint controllers are not registered as aliases.
111+
*
112+
* @dataProvider incompatible_endpoint_class_provider
113+
*
114+
* @param string $legacy Pre-4.7.0 endpoint class name.
115+
*/
116+
public function test_incompatible_endpoint_class_is_not_aliased( $legacy ) {
117+
$this->assertFalse( class_exists( $legacy ), "Incompatible endpoint class {$legacy} must not be aliased." );
118+
}
119+
120+
}

0 commit comments

Comments
 (0)