Skip to content

Commit 56a7fbc

Browse files
authored
Merge pull request #1737 from equalizedigital/william/no-issue/fix-subsite-multisite-saving-from-frontend-and-full-site-scanner
Use a rest api url that works on subsites as well as if someone changes the rest api base
2 parents 6789814 + ce8674d commit 56a7fbc

6 files changed

Lines changed: 222 additions & 5 deletions

File tree

admin/class-enqueue-admin.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ public static function maybe_enqueue_admin_and_editor_app_scripts() {
8787
[
8888
'postID' => $post_id,
8989
'nonce' => wp_create_nonce( 'ajax-nonce' ),
90-
'edacApiUrl' => esc_url_raw( rest_url() . 'accessibility-checker/v1' ),
90+
'edacApiUrl' => esc_url_raw( rest_url( 'accessibility-checker/v1' ) ),
91+
'fixesRestUrl' => esc_url_raw( rest_url( 'edac/v1' ) ),
9192
'restNonce' => wp_create_nonce( 'wp_rest' ),
9293
'proUrl' => esc_url_raw( edac_generate_link_type( [ 'utm_content' => '__name__' ] ) ),
9394
'hasDismissEndpoint' => method_exists( \EDAC\Inc\REST_Api::class, 'dismiss_issue' ),

includes/classes/class-enqueue-frontend.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ public static function maybe_enqueue_frontend_highlighter() {
156156
'isPro' => edac_is_pro(),
157157
'userCanEdit' => current_user_can( 'edit_post', $post_id ),
158158
'edacUrl' => esc_url_raw( get_site_url() ),
159+
'restUrl' => esc_url_raw( rest_url( 'accessibility-checker/v1' ) ),
160+
'fixesRestUrl' => esc_url_raw( rest_url( 'edac/v1' ) ),
159161
'ajaxurl' => admin_url( 'admin-ajax.php' ),
160162
'loggedIn' => is_user_logged_in(),
161163
'appCssUrl' => EDAC_PLUGIN_URL . 'build/css/frontendHighlighterApp.css?ver=' . EDAC_VERSION,

src/common/saveFixSettingsRest.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,22 @@ export const saveFixSettings = ( fixSettingsContainer ) => {
4141
liveRegion.innerText = __( 'Saving...', 'accessibility-checker' );
4242
}
4343

44+
const fixesRestUrl = window.edacFrontendHighlighterApp?.fixesRestUrl ?? window.edac_script_vars?.fixesRestUrl;
45+
46+
if ( ! fixesRestUrl ) {
47+
fixSettingsContainer.classList.remove( 'edac-fix-settings--saving' );
48+
fixButtons.forEach( ( button ) => {
49+
button.disabled = false;
50+
} );
51+
fixSettingsContainer.classList.add( 'edac-fix-settings--saved--error' );
52+
if ( liveRegion ) {
53+
liveRegion.innerText = __( 'Saving failed: Missing REST API URL.', 'accessibility-checker' );
54+
}
55+
return;
56+
}
57+
4458
// make a rest call to save the settings
45-
fetch( '/wp-json/edac/v1/fixes/update/', {
59+
fetch( `${ fixesRestUrl }/fixes/update/`, {
4660
method: 'POST',
4761
headers: {
4862
'Content-Type': 'application/json',

src/frontendHighlighterApp/index.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1871,7 +1871,11 @@ class AccessibilityCheckerHighlight {
18711871

18721872
saveScanResults( postId, nonce, violations, densityMetrics ) {
18731873
const self = this;
1874-
return fetch( '/wp-json/accessibility-checker/v1/post-scan-results/' + postId, {
1874+
const restUrl = window.edacFrontendHighlighterApp?.restUrl;
1875+
if ( ! restUrl ) {
1876+
return Promise.reject( new Error( 'Missing REST API URL.' ) );
1877+
}
1878+
return fetch( `${ restUrl }/post-scan-results/${ postId }`, {
18751879
method: 'POST',
18761880
headers: {
18771881
'Content-Type': 'application/json',
@@ -1951,7 +1955,7 @@ class AccessibilityCheckerHighlight {
19511955
}
19521956

19531957
// Validate required parameters
1954-
if ( ! edacFrontendHighlighterApp?.edacUrl || ! edacFrontendHighlighterApp?.postID ) {
1958+
if ( ! window.edacFrontendHighlighterApp?.restUrl || ! window.edacFrontendHighlighterApp?.postID ) {
19551959
const summary = document.querySelector( '.edac-highlight-panel-controls-summary' );
19561960
if ( summary ) {
19571961
summary.textContent = __( 'Error: Missing required parameters.', 'accessibility-checker' );
@@ -1964,7 +1968,7 @@ class AccessibilityCheckerHighlight {
19641968
this.clearIssuesButton.textContent = __( 'Clearing...', 'accessibility-checker' );
19651969
const summary = document.querySelector( '.edac-highlight-panel-controls-summary' );
19661970

1967-
fetch( `${ edacFrontendHighlighterApp.edacUrl }/wp-json/accessibility-checker/v1/clear-issues/${ edacFrontendHighlighterApp.postID }`, {
1971+
fetch( `${ window.edacFrontendHighlighterApp.restUrl }/clear-issues/${ window.edacFrontendHighlighterApp.postID }`, {
19681972
method: 'POST',
19691973
headers: {
19701974
'Content-Type': 'application/json',

tests/phpunit/Admin/EnqueueAdminTest.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,76 @@ public function testLocalizedProUrlUsesUnderscoreUtmContentKey() {
111111
$this->assertStringNotContainsString( 'utm-content=__name__', $localized_data );
112112
}
113113

114+
/**
115+
* FixesRestUrl is present in the edac_script_vars localized to the admin script.
116+
*/
117+
public function testLocalizedAdminDataIncludesFixesRestUrl(): void {
118+
global $wp_scripts;
119+
120+
$this->enqueue_admin::maybe_enqueue_admin_and_editor_app_scripts();
121+
122+
$localized_data = (string) $wp_scripts->get_data( 'edac', 'data' );
123+
124+
$this->assertNotEmpty( $localized_data );
125+
$this->assertStringContainsString( 'fixesRestUrl', $localized_data );
126+
}
127+
128+
/**
129+
* FixesRestUrl uses the edac/v1 namespace and matches rest_url().
130+
*/
131+
public function testAdminFixesRestUrlContainsEdacV1Namespace(): void {
132+
global $wp_scripts;
133+
134+
$this->enqueue_admin::maybe_enqueue_admin_and_editor_app_scripts();
135+
136+
$localized_data = (string) $wp_scripts->get_data( 'edac', 'data' );
137+
$expected = rest_url( 'edac/v1' );
138+
139+
$this->assertStringContainsString( 'edac', $localized_data );
140+
$this->assertStringContainsString( (string) wp_parse_url( $expected, PHP_URL_HOST ), $localized_data );
141+
}
142+
143+
/**
144+
* FixesRestUrl must be an absolute URL — a root-relative /wp-json path would break
145+
* subdomain multisite installs by resolving to the main site instead of the subsite.
146+
*/
147+
public function testAdminFixesRestUrlIsAbsolute(): void {
148+
global $wp_scripts;
149+
150+
$this->enqueue_admin::maybe_enqueue_admin_and_editor_app_scripts();
151+
152+
$localized_data = (string) $wp_scripts->get_data( 'edac', 'data' );
153+
154+
$this->assertDoesNotMatchRegularExpression( '/"fixesRestUrl"\s*:\s*"\\\\?\/wp-json/', $localized_data );
155+
$this->assertMatchesRegularExpression( '/"fixesRestUrl"\s*:\s*"https?/', $localized_data );
156+
}
157+
158+
/**
159+
* FixesRestUrl in edac_script_vars must follow a custom REST base prefix set via
160+
* the rest_url_prefix filter. Verifies the URL is built with rest_url() rather than
161+
* a hardcoded /wp-json/ string.
162+
* Pretty permalinks are required for the prefix filter to be applied.
163+
*/
164+
public function testAdminFixesRestUrlRespectsCustomRestPrefix(): void {
165+
global $wp_scripts;
166+
167+
update_option( 'permalink_structure', '/%postname%/' );
168+
flush_rewrite_rules(); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.flush_rewrite_rules_flush_rewrite_rules
169+
170+
$prefix_callback = static fn() => 'custom-api';
171+
add_filter( 'rest_url_prefix', $prefix_callback );
172+
173+
$this->enqueue_admin::maybe_enqueue_admin_and_editor_app_scripts();
174+
175+
remove_filter( 'rest_url_prefix', $prefix_callback );
176+
delete_option( 'permalink_structure' );
177+
178+
$localized_data = (string) $wp_scripts->get_data( 'edac', 'data' );
179+
180+
$this->assertStringContainsString( 'custom-api', $localized_data );
181+
$this->assertStringNotContainsString( 'wp-json', $localized_data );
182+
}
183+
114184
/**
115185
* Test that the base script and editor script is enqueued in the editor for an existing page.
116186
*

tests/phpunit/includes/classes/EnqueueFrontendTest.php

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,132 @@ public function testScannerBundleUrlIncludesVersionQueryString(): void {
7373
$this->assertStringContainsString( 'ver=' . EDAC_VERSION, $localized_data );
7474
}
7575

76+
/**
77+
* Helper: enqueue the frontend highlighter as an admin and return the localized data string.
78+
*
79+
* @return string The raw JS localized-data string for edac-frontend-highlighter-app.
80+
*/
81+
private function enqueueAndGetLocalizedData(): string {
82+
$admin_id = $this->factory()->user->create( [ 'role' => 'administrator' ] );
83+
wp_set_current_user( $admin_id );
84+
85+
global $post;
86+
$post = $this->factory()->post->create_and_get( [ 'post_type' => 'post' ] );
87+
88+
Enqueue_Frontend::maybe_enqueue_frontend_highlighter();
89+
90+
global $wp_scripts;
91+
return (string) $wp_scripts->get_data( 'edac-frontend-highlighter-app', 'data' );
92+
}
93+
94+
/**
95+
* RestUrl is present in the localized data passed to the frontend highlighter script.
96+
*/
97+
public function testLocalizedDataIncludesRestUrl(): void {
98+
$localized_data = $this->enqueueAndGetLocalizedData();
99+
100+
$this->assertNotEmpty( $localized_data );
101+
$this->assertStringContainsString( 'restUrl', $localized_data );
102+
}
103+
104+
/**
105+
* RestUrl uses the accessibility-checker/v1 namespace and matches rest_url().
106+
*/
107+
public function testRestUrlMatchesRestUrlFunction(): void {
108+
$localized_data = $this->enqueueAndGetLocalizedData();
109+
$expected = rest_url( 'accessibility-checker/v1' );
110+
111+
$this->assertStringContainsString( 'accessibility-checker', $localized_data );
112+
$this->assertStringContainsString( 'v1', $localized_data );
113+
// The URL must be derived from rest_url(), not hardcoded — verify the host is present.
114+
$this->assertStringContainsString( (string) wp_parse_url( $expected, PHP_URL_HOST ), $localized_data );
115+
}
116+
117+
/**
118+
* RestUrl must be an absolute URL, not a root-relative path like /wp-json/...
119+
* A root-relative URL on a subdomain multisite would resolve to the main site.
120+
*/
121+
public function testRestUrlIsAbsolute(): void {
122+
$localized_data = $this->enqueueAndGetLocalizedData();
123+
124+
// The value following "restUrl" must not be a bare /wp-json path.
125+
$this->assertDoesNotMatchRegularExpression( '/"restUrl"\s*:\s*"\\\\?\/wp-json/', $localized_data );
126+
// And the scheme must be present.
127+
$this->assertMatchesRegularExpression( '/"restUrl"\s*:\s*"https?/', $localized_data );
128+
}
129+
130+
/**
131+
* FixesRestUrl is present in the localized data passed to the frontend highlighter script.
132+
*/
133+
public function testLocalizedDataIncludesFixesRestUrl(): void {
134+
$localized_data = $this->enqueueAndGetLocalizedData();
135+
136+
$this->assertStringContainsString( 'fixesRestUrl', $localized_data );
137+
}
138+
139+
/**
140+
* FixesRestUrl uses the edac/v1 namespace and matches rest_url().
141+
*/
142+
public function testFixesRestUrlContainsEdacV1Namespace(): void {
143+
$localized_data = $this->enqueueAndGetLocalizedData();
144+
$expected = rest_url( 'edac/v1' );
145+
146+
$this->assertStringContainsString( 'edac', $localized_data );
147+
$this->assertStringContainsString( (string) wp_parse_url( $expected, PHP_URL_HOST ), $localized_data );
148+
}
149+
150+
/**
151+
* FixesRestUrl must be an absolute URL, not a root-relative path.
152+
*/
153+
public function testFixesRestUrlIsAbsolute(): void {
154+
$localized_data = $this->enqueueAndGetLocalizedData();
155+
156+
$this->assertDoesNotMatchRegularExpression( '/"fixesRestUrl"\s*:\s*"\\\\?\/wp-json/', $localized_data );
157+
$this->assertMatchesRegularExpression( '/"fixesRestUrl"\s*:\s*"https?/', $localized_data );
158+
}
159+
160+
/**
161+
* RestUrl must follow a custom REST base prefix set via the rest_url_prefix filter.
162+
* Verifies the URL is built with rest_url() rather than a hardcoded /wp-json/ string.
163+
* Pretty permalinks are required for the prefix filter to be applied.
164+
*/
165+
public function testRestUrlRespectsCustomRestPrefix(): void {
166+
update_option( 'permalink_structure', '/%postname%/' );
167+
flush_rewrite_rules(); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.flush_rewrite_rules_flush_rewrite_rules
168+
169+
$prefix_callback = static fn() => 'custom-api';
170+
add_filter( 'rest_url_prefix', $prefix_callback );
171+
$this->added_filters['rest_url_prefix'] = $prefix_callback;
172+
173+
$localized_data = $this->enqueueAndGetLocalizedData();
174+
175+
remove_filter( 'rest_url_prefix', $prefix_callback );
176+
delete_option( 'permalink_structure' );
177+
178+
$this->assertStringContainsString( 'custom-api', $localized_data );
179+
$this->assertStringNotContainsString( 'wp-json', $localized_data );
180+
}
181+
182+
/**
183+
* FixesRestUrl must follow a custom REST base prefix set via the rest_url_prefix filter.
184+
*/
185+
public function testFixesRestUrlRespectsCustomRestPrefix(): void {
186+
update_option( 'permalink_structure', '/%postname%/' );
187+
flush_rewrite_rules(); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.flush_rewrite_rules_flush_rewrite_rules
188+
189+
$prefix_callback = static fn() => 'custom-api';
190+
add_filter( 'rest_url_prefix', $prefix_callback );
191+
$this->added_filters['rest_url_prefix'] = $prefix_callback;
192+
193+
$localized_data = $this->enqueueAndGetLocalizedData();
194+
195+
remove_filter( 'rest_url_prefix', $prefix_callback );
196+
delete_option( 'permalink_structure' );
197+
198+
$this->assertStringContainsString( 'custom-api', $localized_data );
199+
$this->assertStringNotContainsString( 'wp-json', $localized_data );
200+
}
201+
76202
/**
77203
* Ensure the highlighter uses the filtered post ID when determining scannable post types.
78204
*/

0 commit comments

Comments
 (0)