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)
];