Skip to content

Commit 2e54814

Browse files
committed
feat: refactor version switch functions and improve error handling
1 parent fcd6831 commit 2e54814

File tree

1 file changed

+224
-56
lines changed

1 file changed

+224
-56
lines changed

src/php/settings/version-switch.php

Lines changed: 224 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
/**
33
* Handles version switching functionality for the Code Snippets plugin.
44
*
5+
* This file provides a complete version switching system that allows users to:
6+
* - View available plugin versions from WordPress.org
7+
* - Switch between different versions safely
8+
* - Track progress during version switching
9+
* - Handle errors gracefully with detailed logging
10+
*
511
* @package Code_Snippets
612
* @subpackage Settings
713
*/
@@ -10,17 +16,23 @@
1016

1117
use function Code_Snippets\code_snippets;
1218

19+
// Configuration constants for version switching
20+
const VERSION_CACHE_KEY = 'code_snippets_available_versions';
21+
const PROGRESS_KEY = 'code_snippets_version_switch_progress';
22+
const VERSION_CACHE_DURATION = HOUR_IN_SECONDS;
23+
const PROGRESS_TIMEOUT = 5 * MINUTE_IN_SECONDS;
24+
const WORDPRESS_API_ENDPOINT = 'https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&slug=code-snippets';
25+
1326
/**
1427
* Get available plugin versions from WordPress.org repository
1528
*
1629
* @return array Array of version information
1730
*/
1831
function get_available_versions(): array {
19-
$transient_key = 'code_snippets_available_versions';
20-
$versions = get_transient( $transient_key );
32+
$versions = get_transient( VERSION_CACHE_KEY );
2133

2234
if ( false === $versions ) {
23-
$response = wp_remote_get( 'https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&slug=code-snippets' );
35+
$response = wp_remote_get( WORDPRESS_API_ENDPOINT );
2436

2537
if ( is_wp_error( $response ) ) {
2638
return [];
@@ -49,8 +61,8 @@ function get_available_versions(): array {
4961
return version_compare( $b['version'], $a['version'] );
5062
});
5163

52-
// Cache for 1 hour
53-
set_transient( $transient_key, $versions, HOUR_IN_SECONDS );
64+
// Cache for configured duration
65+
set_transient( VERSION_CACHE_KEY, $versions, VERSION_CACHE_DURATION );
5466
}
5567

5668
return $versions;
@@ -71,55 +83,80 @@ function get_current_version(): string {
7183
* @return bool True if switch is in progress
7284
*/
7385
function is_version_switch_in_progress(): bool {
74-
return get_transient( 'code_snippets_version_switch_progress' ) !== false;
86+
return get_transient( PROGRESS_KEY ) !== false;
7587
}
7688

7789
/**
78-
* Handle version switch request
90+
* Clear version-related caches
7991
*
80-
* @param string $target_version Target version to switch to
81-
* @return array Result array with success status and message
92+
* @return void
8293
*/
83-
function handle_version_switch( string $target_version ): array {
84-
// Check user capabilities
85-
if ( ! current_user_can( 'update_plugins' ) ) {
94+
function clear_version_caches(): void {
95+
delete_transient( VERSION_CACHE_KEY );
96+
delete_transient( PROGRESS_KEY );
97+
}
98+
99+
/**
100+
* Validate target version against available versions
101+
*
102+
* @param string $target_version Target version to validate
103+
* @param array $available_versions Array of available versions
104+
* @return array Validation result with success status, message, and download URL
105+
*/
106+
function validate_target_version( string $target_version, array $available_versions ): array {
107+
if ( empty( $target_version ) ) {
86108
return [
87109
'success' => false,
88-
'message' => __( 'You do not have permission to update plugins.', 'code-snippets' ),
110+
'message' => __( 'No target version specified.', 'code-snippets' ),
111+
'download_url' => '',
89112
];
90113
}
91114

92-
// Validate target version
93-
$available_versions = get_available_versions();
94-
$version_exists = false;
95-
$download_url = '';
96-
97115
foreach ( $available_versions as $version_info ) {
98116
if ( $version_info['version'] === $target_version ) {
99-
$version_exists = true;
100-
$download_url = $version_info['url'];
101-
break;
117+
return [
118+
'success' => true,
119+
'message' => '',
120+
'download_url' => $version_info['url'],
121+
];
102122
}
103123
}
104124

105-
if ( ! $version_exists ) {
106-
return [
107-
'success' => false,
108-
'message' => __( 'Invalid version specified.', 'code-snippets' ),
109-
];
110-
}
125+
return [
126+
'success' => false,
127+
'message' => __( 'Invalid version specified.', 'code-snippets' ),
128+
'download_url' => '',
129+
];
130+
}
111131

112-
// Check if already on target version
113-
if ( get_current_version() === $target_version ) {
114-
return [
115-
'success' => false,
116-
'message' => __( 'Already on the specified version.', 'code-snippets' ),
117-
];
132+
/**
133+
* Create a standardized error response
134+
*
135+
* @param string $message User-friendly error message
136+
* @param string $technical_details Technical details for debugging (optional)
137+
* @return array Error response array
138+
*/
139+
function create_error_response( string $message, string $technical_details = '' ): array {
140+
if ( ! empty( $technical_details ) ) {
141+
// Log technical details for debugging
142+
if ( function_exists( 'error_log' ) ) {
143+
error_log( sprintf( 'Code Snippets version switch error: %s. Details: %s', $message, $technical_details ) );
144+
}
118145
}
119146

120-
// Set switch in progress
121-
set_transient( 'code_snippets_version_switch_progress', $target_version, 5 * MINUTE_IN_SECONDS );
147+
return [
148+
'success' => false,
149+
'message' => $message,
150+
];
151+
}
122152

153+
/**
154+
* Perform the actual version installation using WordPress upgrader
155+
*
156+
* @param string $download_url URL to download the plugin version
157+
* @return bool|\WP_Error Installation result
158+
*/
159+
function perform_version_install( string $download_url ) {
123160
// Include WordPress upgrade functions
124161
if ( ! function_exists( 'wp_update_plugins' ) ) {
125162
require_once ABSPATH . 'wp-admin/includes/update.php';
@@ -131,28 +168,161 @@ function handle_version_switch( string $target_version ): array {
131168
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
132169
}
133170

134-
// Create upgrader instance
135-
$upgrader = new \Plugin_Upgrader( new \Automatic_Upgrader_Skin() );
171+
// Create update handler (captures Ajax responses and errors) and upgrader instance
172+
$update_handler = new \WP_Ajax_Upgrader_Skin();
173+
$upgrader = new \Plugin_Upgrader( $update_handler );
136174

137-
// Perform the upgrade/downgrade
138-
$result = $upgrader->upgrade( plugin_basename( CODE_SNIPPETS_FILE ), [
139-
'clear_destination' => true,
140-
'overwrite_package' => true,
175+
// Store the handler globally so we can access it later for error extraction
176+
global $code_snippets_last_update_handler, $code_snippets_last_upgrader;
177+
$code_snippets_last_update_handler = $update_handler;
178+
$code_snippets_last_upgrader = $upgrader;
179+
180+
// Perform the install/overwrite using the package download URL from WordPress.org
181+
return $upgrader->install( $download_url, [
182+
'overwrite_package' => true,
183+
'clear_update_cache' => true,
141184
] );
185+
}
186+
187+
/**
188+
* Handle installation failure and extract useful error information
189+
*
190+
* @param string $target_version The target version that failed to install
191+
* @param string $download_url The download URL used
192+
* @param mixed $install_result The result from the upgrader
193+
* @return array Error response with extracted information
194+
*/
195+
function handle_installation_failure( string $target_version, string $download_url, $install_result ): array {
196+
global $code_snippets_last_update_handler, $code_snippets_last_upgrader;
197+
198+
$handler_messages = extract_handler_messages( $code_snippets_last_update_handler, $code_snippets_last_upgrader );
199+
200+
// Log details for server-side debugging
201+
log_version_switch_attempt( $target_version, $install_result, "URL: $download_url, Messages: $handler_messages" );
202+
203+
// Return a more informative message when possible (still user-friendly)
204+
$fallback_message = __( 'Failed to switch versions. Please try again.', 'code-snippets' );
205+
if ( ! empty( $handler_messages ) ) {
206+
// Trim and sanitize a bit for output
207+
$short = wp_trim_words( wp_strip_all_tags( $handler_messages ), 40, '...' );
208+
$fallback_message = sprintf( '%s %s', $fallback_message, $short );
209+
}
210+
211+
return [
212+
'success' => false,
213+
'message' => $fallback_message,
214+
];
215+
}
216+
217+
/**
218+
* Extract helpful messages from the update handler
219+
*
220+
* @param mixed $update_handler The WP_Ajax_Upgrader_Skin instance
221+
* @param mixed $upgrader The Plugin_Upgrader instance
222+
* @return string Extracted messages
223+
*/
224+
function extract_handler_messages( $update_handler, $upgrader ): string {
225+
$handler_messages = '';
226+
227+
if ( isset( $update_handler ) ) {
228+
// Errors (WP_Ajax_Upgrader_Skin stores them)
229+
if ( method_exists( $update_handler, 'get_errors' ) ) {
230+
$errs = $update_handler->get_errors();
231+
if ( $errs instanceof \WP_Error && $errs->has_errors() ) {
232+
$handler_messages .= implode( "\n", $errs->get_error_messages() );
233+
}
234+
}
235+
// Error messages string
236+
if ( method_exists( $update_handler, 'get_error_messages' ) ) {
237+
$em = $update_handler->get_error_messages();
238+
if ( $em ) {
239+
$handler_messages .= "\n" . $em;
240+
}
241+
}
242+
// Upgrade messages (feedback/info)
243+
if ( method_exists( $update_handler, 'get_upgrade_messages' ) ) {
244+
$upgrade_msgs = $update_handler->get_upgrade_messages();
245+
if ( is_array( $upgrade_msgs ) ) {
246+
$handler_messages .= "\n" . implode( "\n", $upgrade_msgs );
247+
} elseif ( $upgrade_msgs ) {
248+
$handler_messages .= "\n" . (string) $upgrade_msgs;
249+
}
250+
}
251+
}
252+
253+
// Fallback: if upgrader populated result with info, include it
254+
if ( empty( $handler_messages ) && isset( $upgrader->result ) ) {
255+
if ( is_wp_error( $upgrader->result ) ) {
256+
$handler_messages = implode( "\n", $upgrader->result->get_error_messages() );
257+
} else {
258+
$handler_messages = is_scalar( $upgrader->result ) ? (string) $upgrader->result : print_r( $upgrader->result, true );
259+
}
260+
}
261+
262+
return trim( $handler_messages );
263+
}
264+
265+
/**
266+
* Log version switch attempt for debugging
267+
*
268+
* @param string $target_version Target version
269+
* @param mixed $result Installation result
270+
* @param string $details Additional details
271+
* @return void
272+
*/
273+
function log_version_switch_attempt( string $target_version, $result, string $details = '' ): void {
274+
if ( function_exists( 'error_log' ) ) {
275+
error_log( sprintf(
276+
'Code Snippets version switch failed. target=%s, result=%s, details=%s',
277+
$target_version,
278+
var_export( $result, true ),
279+
$details
280+
) );
281+
}
282+
}
283+
284+
/**
285+
* Handle version switch request
286+
*
287+
* @param string $target_version Target version to switch to
288+
* @return array Result array with success status and message
289+
*/
290+
function handle_version_switch( string $target_version ): array {
291+
// Check user capabilities
292+
if ( ! current_user_can( 'update_plugins' ) ) {
293+
return create_error_response( __( 'You do not have permission to update plugins.', 'code-snippets' ) );
294+
}
295+
296+
// Validate target version
297+
$available_versions = get_available_versions();
298+
$validation = validate_target_version( $target_version, $available_versions );
299+
300+
if ( ! $validation['success'] ) {
301+
return create_error_response( $validation['message'] );
302+
}
303+
304+
// Check if already on target version
305+
if ( get_current_version() === $target_version ) {
306+
return create_error_response( __( 'Already on the specified version.', 'code-snippets' ) );
307+
}
308+
309+
// Set switch in progress
310+
set_transient( PROGRESS_KEY, $target_version, PROGRESS_TIMEOUT );
311+
312+
// Perform the version installation
313+
$install_result = perform_version_install( $validation['download_url'] );
142314

143315
// Clear progress transient
144-
delete_transient( 'code_snippets_version_switch_progress' );
316+
delete_transient( PROGRESS_KEY );
145317

146-
if ( is_wp_error( $result ) ) {
147-
return [
148-
'success' => false,
149-
'message' => $result->get_error_message(),
150-
];
318+
// Handle the result
319+
if ( is_wp_error( $install_result ) ) {
320+
return create_error_response( $install_result->get_error_message() );
151321
}
152322

153-
if ( $result ) {
154-
// Clear version cache
155-
delete_transient( 'code_snippets_available_versions' );
323+
if ( $install_result ) {
324+
// Clear version cache on success
325+
delete_transient( VERSION_CACHE_KEY );
156326

157327
return [
158328
'success' => true,
@@ -163,10 +333,8 @@ function handle_version_switch( string $target_version ): array {
163333
];
164334
}
165335

166-
return [
167-
'success' => false,
168-
'message' => __( 'Failed to switch versions. Please try again.', 'code-snippets' ),
169-
];
336+
// If we get here, the installation failed but didn't return a WP_Error
337+
return handle_installation_failure( $target_version, $validation['download_url'], $install_result );
170338
}
171339

172340
/**
@@ -371,8 +539,8 @@ function ajax_refresh_versions(): void {
371539
] );
372540
}
373541

374-
// Clear the cache
375-
delete_transient( 'code_snippets_available_versions' );
542+
// Clear the cache using our helper function
543+
delete_transient( VERSION_CACHE_KEY );
376544

377545
// Fetch fresh data
378546
get_available_versions();

0 commit comments

Comments
 (0)