diff --git a/bulk-plugin-installer.php b/bulk-plugin-installer.php index 2453959..48a5cc9 100644 --- a/bulk-plugin-installer.php +++ b/bulk-plugin-installer.php @@ -3,8 +3,8 @@ * Plugin Name: Bulk Plugin Installer * Plugin URI: https://wpmultisite.com/plugins/bulk-plugin-installer/ * Description: Bulk install WordPress plugins and themes from repository, URL, or ZIP uploads. - * Version: 1.1.6 - * Author: WPMultisite.com + * Version: 1.1.7 + * Author: WPMultisite.com * Author URI: https://wpmultisite.com * Network: true * Requires at least: 5.8 @@ -18,7 +18,7 @@ if (!defined('WPINC')) { die; } -define('BPI_VERSION', '1.1.6'); +define('BPI_VERSION', '1.1.7'); define('BPI_PATH', plugin_dir_path(__FILE__)); define('BPI_URL', plugin_dir_url(__FILE__)); @@ -26,6 +26,8 @@ require_once BPI_PATH . 'includes/class-installer.php'; require_once BPI_PATH . 'includes/admin-page.php'; function bpi_init() { + load_plugin_textdomain('bulk-plugin-installer', false, dirname(plugin_basename(__FILE__)) . '/languages/'); + if (is_multisite()) { if (is_network_admin()) { add_action('network_admin_menu', 'bpi_add_network_submenu_page'); @@ -143,7 +145,10 @@ function bpi_handle_install_plugins() { check_ajax_referer('bpi_installer', 'nonce'); if (!current_user_can('install_plugins') && !(is_multisite() && current_user_can('manage_network_plugins'))) { - wp_send_json_error(__('Insufficient permissions', 'bulk-plugin-installer')); + wp_send_json_error([ + 'message' => __('Insufficient permissions', 'bulk-plugin-installer'), + 'error_code' => 403 + ]); } $installer = new BPI_Installer(); @@ -153,7 +158,10 @@ function bpi_handle_install_plugins() { try { if ($type === 'upload') { if (!isset($_FILES['plugin_files']) || empty($_FILES['plugin_files']['name'])) { - wp_send_json_error(__('No files uploaded', 'bulk-plugin-installer')); + wp_send_json_error([ + 'message' => __('No files uploaded', 'bulk-plugin-installer'), + 'error_code' => 400 + ]); } $files = []; if (is_array($_FILES['plugin_files']['name'])) { @@ -181,13 +189,19 @@ function bpi_handle_install_plugins() { } } if (empty($files)) { - wp_send_json_error(__('No valid files uploaded', 'bulk-plugin-installer')); + wp_send_json_error([ + 'message' => __('No valid files uploaded', 'bulk-plugin-installer'), + 'error_code' => 400 + ]); } $results = $installer->bpi_install_plugins($files, $type); } else { $items = isset($_POST['items']) ? json_decode(stripslashes($_POST['items']), true) : []; if (!is_array($items) || empty($items)) { - wp_send_json_error(__('No items provided', 'bulk-plugin-installer')); + wp_send_json_error([ + 'message' => __('No items provided', 'bulk-plugin-installer'), + 'error_code' => 400 + ]); } $results = $installer->bpi_install_plugins($items, $type); } @@ -195,7 +209,10 @@ function bpi_handle_install_plugins() { wp_send_json_success($results); } catch (Exception $e) { error_log('BPI Plugin Install Error: ' . $e->getMessage()); - wp_send_json_error(__('Installation failed: ', 'bulk-plugin-installer') . $e->getMessage()); + wp_send_json_error([ + 'message' => $installer->get_error_message($e->getCode(), $e->getMessage()), + 'error_code' => $e->getCode() + ]); } } @@ -203,7 +220,10 @@ function bpi_handle_install_themes() { check_ajax_referer('bpi_installer', 'nonce'); if (!current_user_can('install_themes') && !(is_multisite() && current_user_can('manage_network_plugins'))) { - wp_send_json_error(__('Insufficient permissions', 'bulk-plugin-installer')); + wp_send_json_error([ + 'message' => __('Insufficient permissions', 'bulk-plugin-installer'), + 'error_code' => 403 + ]); } $installer = new BPI_Installer(); @@ -213,7 +233,10 @@ function bpi_handle_install_themes() { try { if ($type === 'upload') { if (!isset($_FILES['theme_files']) || empty($_FILES['theme_files']['name'])) { - wp_send_json_error(__('No files uploaded', 'bulk-plugin-installer')); + wp_send_json_error([ + 'message' => __('No files uploaded', 'bulk-plugin-installer'), + 'error_code' => 400 + ]); } $files = []; if (is_array($_FILES['theme_files']['name'])) { @@ -241,13 +264,19 @@ function bpi_handle_install_themes() { } } if (empty($files)) { - wp_send_json_error(__('No valid files uploaded', 'bulk-plugin-installer')); + wp_send_json_error([ + 'message' => __('No valid files uploaded', 'bulk-plugin-installer'), + 'error_code' => 400 + ]); } $results = $installer->bpi_install_themes($files, $type); } else { $items = isset($_POST['items']) ? json_decode(stripslashes($_POST['items']), true) : []; if (!is_array($items) || empty($items)) { - wp_send_json_error(__('No items provided', 'bulk-plugin-installer')); + wp_send_json_error([ + 'message' => __('No items provided', 'bulk-plugin-installer'), + 'error_code' => 400 + ]); } $results = $installer->bpi_install_themes($items, $type); } @@ -255,7 +284,10 @@ function bpi_handle_install_themes() { wp_send_json_success($results); } catch (Exception $e) { error_log('BPI Theme Install Error: ' . $e->getMessage()); - wp_send_json_error(__('Installation failed: ', 'bulk-plugin-installer') . $e->getMessage()); + wp_send_json_error([ + 'message' => $installer->get_error_message($e->getCode(), $e->getMessage()), + 'error_code' => $e->getCode() + ]); } } @@ -263,7 +295,10 @@ function bpi_handle_save_settings() { check_ajax_referer('bpi_installer', 'nonce'); if (!current_user_can('manage_options') && !(is_multisite() && current_user_can('manage_network_options'))) { - wp_send_json_error(__('Insufficient permissions', 'bulk-plugin-installer')); + wp_send_json_error([ + 'message' => __('Insufficient permissions', 'bulk-plugin-installer'), + 'error_code' => 403 + ]); } $roles = isset($_POST['bpi_allowed_roles']) ? (array)$_POST['bpi_allowed_roles'] : []; @@ -322,4 +357,4 @@ function bpi_activate() { 'last_install_time' => '' ]); } -} +} \ No newline at end of file diff --git a/includes/admin-page.php b/includes/admin-page.php index 9fe6a1c..cf32e3e 100644 --- a/includes/admin-page.php +++ b/includes/admin-page.php @@ -6,9 +6,25 @@ function bpi_render_admin_page() { wp_enqueue_style('bpi-admin-style', BPI_URL . 'assets/css/admin.css', [], BPI_VERSION); wp_enqueue_script('bpi-admin', BPI_URL . 'assets/js/admin.js', ['jquery'], BPI_VERSION, true); + + $i18n_strings = [ + 'go_to_settings' => __('Go to Settings to add domain', 'bulk-plugin-installer'), + 'switch_to_themes' => __('Switch to Themes tab', 'bulk-plugin-installer'), + 'switch_to_plugins' => __('Switch to Plugins tab', 'bulk-plugin-installer'), + 'retry' => __('Retry', 'bulk-plugin-installer'), + 'installation_in_progress' => __('Installation in progress... (Large ZIP files may take some time)', 'bulk-plugin-installer'), + 'completed' => __('completed', 'bulk-plugin-installer'), + 'remaining' => __('remaining', 'bulk-plugin-installer'), + 'installation_complete' => __('Installation completed! Check the results below. Failed items can be retried using the "Retry" buttons if applicable.', 'bulk-plugin-installer'), + 'no_files' => __('Please select at least one ZIP file.', 'bulk-plugin-installer'), + 'no_slugs' => __('Please enter at least one slug.', 'bulk-plugin-installer'), + 'no_urls' => __('Please enter at least one URL.', 'bulk-plugin-installer') + ]; + wp_localize_script('bpi-admin', 'bpiAjax', [ 'nonce' => wp_create_nonce('bpi_installer'), - 'ajaxurl' => admin_url('admin-ajax.php') + 'ajaxurl' => admin_url('admin-ajax.php'), + 'i18n' => $i18n_strings ]); ?>
diff --git a/includes/class-installer.php b/includes/class-installer.php index c31733b..90500fb 100644 --- a/includes/class-installer.php +++ b/includes/class-installer.php @@ -1,6 +1,24 @@ 'Invalid plugin slug format. Please use only lowercase letters, numbers and hyphens.', + 1004 => 'Invalid URL format. Please provide a complete URL including http:// or https://', + 1005 => 'The domain is not in the trusted list. Please add it in settings or use another source.', + 1007 => 'Only ZIP files are allowed for plugin uploads.', + 1009 => 'This appears to be a theme ZIP file. Please use the Themes tab for installation.', + 1010 => 'The uploaded file is not a valid WordPress plugin ZIP.', + + // Theme error codes + 2001 => 'Invalid theme slug format. Please use only lowercase letters, numbers and hyphens.', + 2003 => 'Only ZIP files are allowed for theme uploads.', + 2005 => 'This appears to be a plugin ZIP file. Please use the Plugins tab for installation.', + 2006 => 'The uploaded file is not a valid WordPress theme ZIP.', + 2009 => 'Invalid URL format for theme. Please provide a complete URL including http:// or https://', + 2010 => 'The domain is not in the trusted list for themes. Please add it in settings or use another source.' + ]; public function __construct() { global $wp_filesystem; @@ -13,6 +31,10 @@ class BPI_Installer { $this->wp_filesystem = $wp_filesystem; } + public function get_error_message($code, $default_message) { + return __($this->error_messages[$code] ?? $default_message, 'bulk-plugin-installer'); + } + private function is_plugin_zip($zip_path) { $zip = new ZipArchive(); if ($zip->open($zip_path) === true) { @@ -69,7 +91,10 @@ class BPI_Installer { if ($type === 'repository' || $type === 'wenpai') { $item = sanitize_text_field($item); if (!preg_match('/^[a-z0-9-]+$/', $item)) { - throw new Exception('Invalid plugin slug', 1001); + throw new Exception( + $this->get_error_message(1001, 'Invalid plugin slug'), + 1001 + ); } $plugin_key = $item . '/' . $item . '.php'; } elseif ($type === 'upload') { @@ -94,7 +119,10 @@ class BPI_Installer { 'fields' => ['sections' => false] ]); if (is_wp_error($api)) { - throw new Exception('Failed to fetch plugin info: ' . $api->get_error_message(), 1002); + throw new Exception( + $this->get_error_message(1002, 'Failed to fetch plugin info: ' . $api->get_error_message()), + 1002 + ); } $download_link = $api->download_link; break; @@ -103,7 +131,10 @@ class BPI_Installer { $response = wp_remote_get("https://api.wenpai.net/wp-json/wp/v2/plugins/{$item}"); if (is_wp_error($response)) { $code = wp_remote_retrieve_response_code($response); - throw new Exception("Download failed with status code $code: " . $response->get_error_message(), 1003); + throw new Exception( + $this->get_error_message(1003, "Download failed with status code $code: " . $response->get_error_message()), + 1003 + ); } $plugin_data = json_decode(wp_remote_retrieve_body($response), true); $download_link = $plugin_data && !empty($plugin_data['download_link']) @@ -114,10 +145,16 @@ class BPI_Installer { case 'url': $item = sanitize_text_field($item); if (!filter_var($item, FILTER_VALIDATE_URL)) { - throw new Exception('Invalid URL format', 1004); + throw new Exception( + $this->get_error_message(1004, 'Invalid URL format'), + 1004 + ); } if (!bpi_is_domain_allowed($item)) { - throw new Exception('Untrusted domain', 1005); + throw new Exception( + $this->get_error_message(1005, 'Untrusted domain'), + 1005 + ); } $download_link = $item; break; @@ -125,27 +162,42 @@ class BPI_Installer { case 'upload': $file = $item; if ($file['error'] !== UPLOAD_ERR_OK) { - throw new Exception('File upload error: ' . $file['error'], 1006); + throw new Exception( + $this->get_error_message(1006, 'File upload error: ' . $file['error']), + 1006 + ); } $file_name = sanitize_file_name($file['name']); if (pathinfo($file_name, PATHINFO_EXTENSION) !== 'zip') { - throw new Exception('Only ZIP files are allowed', 1007); + throw new Exception( + $this->get_error_message(1007, 'Only ZIP files are allowed'), + 1007 + ); } $temp_file = $file['tmp_name']; $upload_dir = wp_upload_dir(); $dest_path = $upload_dir['path'] . '/' . $file_name; if (!move_uploaded_file($temp_file, $dest_path)) { - throw new Exception('Failed to move uploaded file. Check server permissions.', 1008); + throw new Exception( + $this->get_error_message(1008, 'Failed to move uploaded file. Check server permissions.'), + 1008 + ); } if (!$this->is_plugin_zip($dest_path)) { if ($this->is_theme_zip($dest_path)) { unlink($dest_path); - throw new Exception('This appears to be a theme ZIP. Please use the Plugins tab to install.', 1009); + throw new Exception( + $this->get_error_message(1009, 'Theme ZIP detected'), + 1009 + ); } unlink($dest_path); - throw new Exception('Invalid plugin ZIP file', 1010); + throw new Exception( + $this->get_error_message(1010, 'Invalid plugin ZIP'), + 1010 + ); } $download_link = $dest_path; @@ -160,20 +212,26 @@ class BPI_Installer { if (is_wp_error($result)) { $error_message = $result->get_error_message(); - throw new Exception($error_message ?: 'Unknown installation error', 1011); + throw new Exception( + $this->get_error_message(1011, $error_message ?: 'Unknown installation error'), + 1011 + ); } elseif ($result !== true) { - throw new Exception('Installation failed unexpectedly', 1012); + throw new Exception( + $this->get_error_message(1012, 'Installation failed unexpectedly'), + 1012 + ); } $results[$item] = [ 'success' => true, - 'message' => 'Successfully installed' + 'message' => __('Successfully installed', 'bulk-plugin-installer') ]; } catch (Exception $e) { - $no_retry_codes = [1001, 1004, 1005, 1007, 1009, 1010]; // 无需重试的错误代码 + $no_retry_codes = [1001, 1004, 1005, 1007, 1009, 1010]; $results[$item] = [ 'success' => false, - 'message' => $e->getMessage(), + 'message' => $this->get_error_message($e->getCode(), $e->getMessage()), 'error_code' => $e->getCode(), 'retry' => !in_array($e->getCode(), $no_retry_codes) ]; @@ -207,24 +265,36 @@ class BPI_Installer { if ($type === 'repository' || $type === 'wenpai') { $item = sanitize_text_field($item); if (!preg_match('/^[a-z0-9-]+$/', $item)) { - throw new Exception('Invalid theme slug', 2001); + throw new Exception( + $this->get_error_message(2001, 'Invalid theme slug'), + 2001 + ); } $theme_key = $item; } elseif ($type === 'upload') { $file = $item; if ($file['error'] !== UPLOAD_ERR_OK) { - throw new Exception('File upload error: ' . $file['error'], 2002); + throw new Exception( + $this->get_error_message(2002, 'File upload error: ' . $file['error']), + 2002 + ); } $file_name = sanitize_file_name($file['name']); if (pathinfo($file_name, PATHINFO_EXTENSION) !== 'zip') { - throw new Exception('Only ZIP files are allowed', 2003); + throw new Exception( + $this->get_error_message(2003, 'Only ZIP files are allowed'), + 2003 + ); } $temp_file = $file['tmp_name']; $upload_dir = wp_upload_dir(); $dest_path = $upload_dir['path'] . '/' . $file_name; if (!move_uploaded_file($temp_file, $dest_path)) { - throw new Exception('Failed to move uploaded file. Check server permissions.', 2004); + throw new Exception( + $this->get_error_message(2004, 'Failed to move uploaded file. Check server permissions.'), + 2004 + ); } $zip = new ZipArchive(); @@ -249,7 +319,7 @@ class BPI_Installer { } $results[$file_name] = [ 'success' => true, - 'message' => 'Theme already installed, skipped', + 'message' => __('Theme already installed, skipped', 'bulk-plugin-installer'), 'skipped' => true ]; continue; @@ -258,10 +328,16 @@ class BPI_Installer { if (!$this->is_theme_zip($dest_path)) { if ($this->is_plugin_zip($dest_path)) { unlink($dest_path); - throw new Exception('This appears to be a plugin ZIP. Please use the Plugins tab to install.', 2005); + throw new Exception( + $this->get_error_message(2005, 'Plugin ZIP detected'), + 2005 + ); } unlink($dest_path); - throw new Exception('Invalid theme ZIP file', 2006); + throw new Exception( + $this->get_error_message(2006, 'Invalid theme ZIP'), + 2006 + ); } $download_link = $dest_path; @@ -271,7 +347,7 @@ class BPI_Installer { if ($theme_key && array_key_exists($theme_key, $installed_themes)) { $results[$item] = [ 'success' => true, - 'message' => 'Theme already installed, skipped', + 'message' => __('Theme already installed, skipped', 'bulk-plugin-installer'), 'skipped' => true ]; continue; @@ -285,7 +361,10 @@ class BPI_Installer { 'fields' => ['sections' => false] ]); if (is_wp_error($api)) { - throw new Exception('Failed to fetch theme info: ' . $api->get_error_message(), 2007); + throw new Exception( + $this->get_error_message(2007, 'Failed to fetch theme info: ' . $api->get_error_message()), + 2007 + ); } $download_link = $api->download_link; break; @@ -294,7 +373,10 @@ class BPI_Installer { $response = wp_remote_get("https://api.wenpai.net/wp-json/wp/v2/themes/{$item}"); if (is_wp_error($response)) { $code = wp_remote_retrieve_response_code($response); - throw new Exception("Download failed with status code $code: " . $response->get_error_message(), 2008); + throw new Exception( + $this->get_error_message(2008, "Download failed with status code $code: " . $response->get_error_message()), + 2008 + ); } $theme_data = json_decode(wp_remote_retrieve_body($response), true); $download_link = $theme_data && !empty($theme_data['download_link']) @@ -305,16 +387,22 @@ class BPI_Installer { case 'url': $item = sanitize_text_field($item); if (!filter_var($item, FILTER_VALIDATE_URL)) { - throw new Exception('Invalid URL format', 2009); + throw new Exception( + $this->get_error_message(2009, 'Invalid URL format'), + 2009 + ); } if (!bpi_is_domain_allowed($item)) { - throw new Exception('Untrusted domain', 2010); + throw new Exception( + $this->get_error_message(2010, 'Untrusted domain'), + 2010 + ); } $download_link = $item; break; case 'upload': - // 已在上方处理 + // Already handled above break; } @@ -325,20 +413,26 @@ class BPI_Installer { if (is_wp_error($result)) { $error_message = $result->get_error_message(); - throw new Exception($error_message ?: 'Unknown installation error', 2011); + throw new Exception( + $this->get_error_message(2011, $error_message ?: 'Unknown installation error'), + 2011 + ); } elseif ($result !== true) { - throw new Exception('Installation failed unexpectedly', 2012); + throw new Exception( + $this->get_error_message(2012, 'Installation failed unexpectedly'), + 2012 + ); } $results[$item] = [ 'success' => true, - 'message' => 'Successfully installed' + 'message' => __('Successfully installed', 'bulk-plugin-installer') ]; } catch (Exception $e) { - $no_retry_codes = [2001, 2003, 2005, 2006, 2009, 2010]; // 无需重试的错误代码 + $no_retry_codes = [2001, 2003, 2005, 2006, 2009, 2010]; $results[$item] = [ 'success' => false, - 'message' => $e->getMessage(), + 'message' => $this->get_error_message($e->getCode(), $e->getMessage()), 'error_code' => $e->getCode(), 'retry' => !in_array($e->getCode(), $no_retry_codes) ];