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 */
1016
1117use 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 */
1831function 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 */
7385function 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