Skip to content

Commit 6d89bd7

Browse files
committed
Merge branch '4.1' into 'main'
2 parents f735434 + b9ab193 commit 6d89bd7

3 files changed

Lines changed: 47 additions & 29 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ This is a log of major user-visible changes in each phpMyFAQ release.
4242

4343
### phpMyFAQ v4.1.1 - unreleased
4444

45+
- updated third party dependencies (Thorsten)
4546
- fixed bugs (Thorsten)
4647

4748
### phpMyFAQ v4.1.0 - 2026-03-12

phpmyfaq/assets/src/configuration/update.test.ts

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ describe('handleUpdateInformation', () => {
9292

9393
it('should do nothing when URL does not end with /update', async () => {
9494
Object.defineProperty(window, 'location', {
95-
value: { href: 'http://localhost/admin' },
95+
value: { href: 'http://localhost/admin', pathname: '/admin' },
9696
writable: true,
9797
});
9898

@@ -105,7 +105,7 @@ describe('handleUpdateInformation', () => {
105105

106106
it('should do nothing when installed version input is missing', async () => {
107107
Object.defineProperty(window, 'location', {
108-
value: { href: 'http://localhost/update' },
108+
value: { href: 'http://localhost/update', pathname: '/update' },
109109
writable: true,
110110
});
111111

@@ -119,7 +119,7 @@ describe('handleUpdateInformation', () => {
119119

120120
it('should show success alert and enable button on successful check', async () => {
121121
Object.defineProperty(window, 'location', {
122-
value: { href: 'http://localhost/update' },
122+
value: { href: 'http://localhost/update', pathname: '/update' },
123123
writable: true,
124124
});
125125

@@ -153,9 +153,9 @@ describe('handleUpdateInformation', () => {
153153
expect(button.disabled).toBe(false);
154154
});
155155

156-
it('should show error alert on failed check response', async () => {
156+
it('should show error alert on failed check response with JSON content type', async () => {
157157
Object.defineProperty(window, 'location', {
158-
value: { href: 'http://localhost/update' },
158+
value: { href: 'http://localhost/update', pathname: '/update' },
159159
writable: true,
160160
});
161161

@@ -167,6 +167,7 @@ describe('handleUpdateInformation', () => {
167167

168168
global.fetch = vi.fn().mockResolvedValue({
169169
ok: false,
170+
headers: { get: (name: string) => (name === 'content-type' ? 'application/json' : null) },
170171
json: () => Promise.resolve({ message: 'Version mismatch' }),
171172
});
172173

@@ -179,9 +180,9 @@ describe('handleUpdateInformation', () => {
179180
expect(result?.innerText).toBe('Version mismatch');
180181
});
181182

182-
it('should show default error message when response has no message', async () => {
183+
it('should show default error message when JSON response has no message', async () => {
183184
Object.defineProperty(window, 'location', {
184-
value: { href: 'http://localhost/update' },
185+
value: { href: 'http://localhost/update', pathname: '/update' },
185186
writable: true,
186187
});
187188

@@ -193,6 +194,7 @@ describe('handleUpdateInformation', () => {
193194

194195
global.fetch = vi.fn().mockResolvedValue({
195196
ok: false,
197+
headers: { get: (name: string) => (name === 'content-type' ? 'application/json' : null) },
196198
json: () => Promise.resolve({}),
197199
});
198200

@@ -202,9 +204,9 @@ describe('handleUpdateInformation', () => {
202204
expect(result?.innerText).toBe('Update check failed');
203205
});
204206

205-
it('should show server config error on SyntaxError', async () => {
207+
it('should show server config error on non-JSON Not Found response', async () => {
206208
Object.defineProperty(window, 'location', {
207-
value: { href: 'http://localhost/update' },
209+
value: { href: 'http://localhost/update', pathname: '/update' },
208210
writable: true,
209211
});
210212

@@ -214,7 +216,11 @@ describe('handleUpdateInformation', () => {
214216
<div id="phpmyfaq-update-check-result"></div>
215217
`;
216218

217-
global.fetch = vi.fn().mockRejectedValue(new SyntaxError('Unexpected token'));
219+
global.fetch = vi.fn().mockResolvedValue({
220+
ok: false,
221+
headers: { get: () => 'text/html' },
222+
text: () => Promise.resolve('Not Found'),
223+
});
218224

219225
await handleUpdateInformation();
220226

@@ -223,9 +229,9 @@ describe('handleUpdateInformation', () => {
223229
expect(result?.innerText).toContain('RewriteBase');
224230
});
225231

226-
it('should show generic error message on other errors', async () => {
232+
it('should show connection error message on network failure', async () => {
227233
Object.defineProperty(window, 'location', {
228-
value: { href: 'http://localhost/update' },
234+
value: { href: 'http://localhost/update', pathname: '/update' },
229235
writable: true,
230236
});
231237

@@ -240,12 +246,12 @@ describe('handleUpdateInformation', () => {
240246
await handleUpdateInformation();
241247

242248
const result = document.getElementById('phpmyfaq-update-check-result');
243-
expect(result?.innerText).toBe('Network failure');
249+
expect(result?.innerText).toContain('Could not connect to the update API');
244250
});
245251

246252
it('should work with URL ending in /update/', async () => {
247253
Object.defineProperty(window, 'location', {
248-
value: { href: 'http://localhost/update/' },
254+
value: { href: 'http://localhost/update/', pathname: '/update/' },
249255
writable: true,
250256
});
251257

phpmyfaq/assets/src/configuration/update.ts

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ export const handleUpdateNextStepButton = (): void => {
3030
};
3131

3232
export const handleUpdateInformation = async (): Promise<void> => {
33-
if (window.location.href.endsWith('/update') || window.location.href.endsWith('/update/')) {
33+
const path = window.location.pathname;
34+
if (path.endsWith('/update') || path.endsWith('/update/') || path.endsWith('/update/index.php')) {
3435
const installedVersion = document.getElementById('phpmyfaq-update-installed-version') as HTMLInputElement | null;
3536

3637
if (!installedVersion) return;
@@ -46,13 +47,26 @@ export const handleUpdateInformation = async (): Promise<void> => {
4647
});
4748

4849
if (!response.ok) {
49-
const errorMessage = await response.json();
50+
let errorText: string;
51+
const contentType = response.headers.get('content-type') || '';
52+
if (contentType.includes('application/json')) {
53+
const errorMessage = await response.json();
54+
errorText = errorMessage.message || errorMessage.error || 'Update check failed';
55+
} else {
56+
errorText = await response.text();
57+
if (!errorText || errorText === 'Not Found') {
58+
errorText =
59+
'The requested resource was not found on the server. ' +
60+
'Please check your server configuration, if you use Apache, the RewriteBase in your .htaccess ' +
61+
'configuration. If you use nginx, please check your nginx rewrite configuration.';
62+
}
63+
}
5064
const alert = document.getElementById('phpmyfaq-update-check-alert') as HTMLElement | null;
5165
const alertResult = document.getElementById('phpmyfaq-update-check-result') as HTMLElement | null;
5266

5367
if (alert && alertResult) {
5468
alert.classList.remove('d-none');
55-
alertResult.innerText = errorMessage.message || 'Update check failed';
69+
alertResult.innerText = errorText;
5670
}
5771
return;
5872
}
@@ -65,16 +79,11 @@ export const handleUpdateInformation = async (): Promise<void> => {
6579
button.classList.remove('disabled');
6680
button.disabled = false;
6781
}
68-
} catch (error: unknown) {
69-
let errorMessage: string;
70-
if (error instanceof SyntaxError) {
71-
errorMessage =
72-
'The requested resource was not found on the server. ' +
73-
'Please check your server configuration, if you use Apache, the RewriteBase in your .htaccess ' +
74-
'configuration. If you use nginx, please check your nginx rewrite configuration.';
75-
} else {
76-
errorMessage = error instanceof Error ? error.message : String(error);
77-
}
82+
} catch {
83+
const errorMessage =
84+
'Could not connect to the update API. Please check your server configuration: ' +
85+
'if you use Apache, verify the RewriteBase in your .htaccess matches your installation path. ' +
86+
'If you use nginx, check your rewrite configuration.';
7887
const alert = document.getElementById('phpmyfaq-update-check-alert') as HTMLElement | null;
7988
const alertResult = document.getElementById('phpmyfaq-update-check-result') as HTMLElement | null;
8089

@@ -87,7 +96,8 @@ export const handleUpdateInformation = async (): Promise<void> => {
8796
};
8897

8998
export const handleConfigBackup = async (): Promise<void> => {
90-
if (window.location.href.endsWith('/update?step=2') || window.location.href.endsWith('/update/?step=2')) {
99+
const href = window.location.href;
100+
if (href.includes('/update') && href.includes('step=2')) {
91101
const installedVersion = document.getElementById('phpmyfaq-update-installed-version') as HTMLInputElement | null;
92102

93103
if (!installedVersion) return;
@@ -115,7 +125,8 @@ export const handleConfigBackup = async (): Promise<void> => {
115125
};
116126

117127
export const handleDatabaseUpdate = async (): Promise<void> => {
118-
if (window.location.href.endsWith('/update?step=3') || window.location.href.endsWith('/update/?step=3')) {
128+
const href = window.location.href;
129+
if (href.includes('/update') && href.includes('step=3')) {
119130
const installedVersion = document.getElementById('phpmyfaq-update-installed-version') as HTMLInputElement | null;
120131

121132
if (!installedVersion) return;

0 commit comments

Comments
 (0)