mirror of
https://github.com/WenPai-org/bulk-plugin-installer.git
synced 2025-08-03 01:58:43 +08:00
调整目录结构,新增服务器安装列表
This commit is contained in:
parent
814e65f90e
commit
c5bdfeabcb
6 changed files with 272 additions and 109 deletions
|
@ -193,3 +193,18 @@
|
|||
border-radius: 4px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
background: #007cba;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 2px 8px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.retry-btn:hover {
|
||||
background: #005a87;
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
jQuery(document).ready(function($) {
|
||||
const $results = $('#installation-results');
|
||||
|
||||
$('.bpi-tab').on('click', function() {
|
||||
$('.bpi-tab').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
|
@ -82,7 +84,6 @@ jQuery(document).ready(function($) {
|
|||
const action = $form.attr('id') === 'bulk-plugin-form' ? 'bpi_install_plugins' : 'bpi_install_themes';
|
||||
const type = $form.find('.bpi-select').val();
|
||||
const $submitButton = $form.find('button[type="submit"]');
|
||||
const $results = $('#installation-results');
|
||||
|
||||
let items = [];
|
||||
let errorMessage = '';
|
||||
|
@ -132,17 +133,19 @@ jQuery(document).ready(function($) {
|
|||
processData: false,
|
||||
contentType: false,
|
||||
success: function(response) {
|
||||
console.log('Upload response:', response); // 调试输出
|
||||
if (response.success) {
|
||||
Object.keys(response.data).forEach((item, index) => {
|
||||
handleResponse(response, item, index);
|
||||
});
|
||||
} else {
|
||||
$list.append(`<li><span class="item-name">Upload Error</span><span class="status error">✗ ${response.data}</span></li>`);
|
||||
$list.append(`<li><span class="item-name">Upload Error</span><span class="status error">✗ ${escapeHtml(response.data || 'Unknown upload error')}</span></li>`);
|
||||
}
|
||||
installationComplete();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
$list.append(`<li><span class="item-name">Upload Error</span><span class="status error">✗ ${xhr.responseText || error}</span></li>`);
|
||||
console.log('Upload error:', xhr, status, error); // 调试输出
|
||||
$list.append(`<li><span class="item-name">Upload Error</span><span class="status error">✗ ${escapeHtml(xhr.responseText || error)}</span></li>`);
|
||||
installationComplete();
|
||||
}
|
||||
});
|
||||
|
@ -169,10 +172,12 @@ jQuery(document).ready(function($) {
|
|||
install_type: type
|
||||
},
|
||||
success: function(response) {
|
||||
console.log('Item response:', response); // 调试输出
|
||||
handleResponse(response, item, index);
|
||||
processNextItem(index + 1);
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.log('Item error:', xhr, status, error); // 调试输出
|
||||
handleError(xhr, status, error, item, index);
|
||||
processNextItem(index + 1);
|
||||
}
|
||||
|
@ -183,12 +188,23 @@ jQuery(document).ready(function($) {
|
|||
const $item = $(`#item-${index}`) || $list.find('li:last');
|
||||
$item.find('.spinner').removeClass('is-active');
|
||||
|
||||
if (response.success) {
|
||||
if (response.success && response.data[item]) {
|
||||
const result = response.data[item];
|
||||
$item.addClass(result.success ? 'success' : 'error')
|
||||
.find('.status').text(result.success ? '✓ ' + result.message : '✗ ' + result.message);
|
||||
$item.addClass(result.success ? 'success' : 'error');
|
||||
let statusHtml = '';
|
||||
if (result.success) {
|
||||
statusHtml = result.skipped ? 'ⓘ ' + escapeHtml(result.message) : '✓ ' + escapeHtml(result.message);
|
||||
} else {
|
||||
statusHtml = '✗ ' + escapeHtml(result.message);
|
||||
if (result.retry) {
|
||||
statusHtml += ' <button class="retry-btn" data-item="' + escapeHtml(item) + '" data-type="' + type + '">Retry</button>';
|
||||
}
|
||||
}
|
||||
$item.find('.status').html(statusHtml);
|
||||
} else {
|
||||
$item.addClass('error').find('.status').text('✗ ' + (response.data || 'Unknown error'));
|
||||
$item.addClass('error')
|
||||
.find('.status')
|
||||
.html('✗ ' + escapeHtml(response.data || 'Unknown error') + ' <button class="retry-btn" data-item="' + escapeHtml(item) + '" data-type="' + type + '">Retry</button>');
|
||||
}
|
||||
|
||||
completed++;
|
||||
|
@ -201,7 +217,8 @@ jQuery(document).ready(function($) {
|
|||
const $item = $(`#item-${index}`) || $list.find('li:last');
|
||||
$item.find('.spinner').removeClass('is-active')
|
||||
.addClass('error')
|
||||
.find('.status').text(`✗ Installation failed: ${xhr.responseText || error}`);
|
||||
.find('.status')
|
||||
.html(`✗ ${escapeHtml(xhr.responseText || 'Installation failed: ' + error)} <button class="retry-btn" data-item="${escapeHtml(item)}" data-type="${type}">Retry</button>`);
|
||||
completed++;
|
||||
const percentage = Math.round((completed / items.length) * 100);
|
||||
const remaining = items.length - completed;
|
||||
|
@ -211,10 +228,60 @@ jQuery(document).ready(function($) {
|
|||
function installationComplete() {
|
||||
$submitButton.prop('disabled', false).text(`Install ${action === 'bpi_install_plugins' ? 'Plugins' : 'Themes'}`);
|
||||
const $notice = $results.find('.notice').removeClass('notice-info').addClass('notice-success');
|
||||
$notice.find('p').text('Installation completed!');
|
||||
$notice.find('p').html('Installation completed! Check the results below. Failed items can be retried using the "Retry" buttons if applicable.');
|
||||
}
|
||||
});
|
||||
|
||||
$results.on('click', '.retry-btn', function() {
|
||||
const $button = $(this);
|
||||
const item = $button.data('item');
|
||||
const type = $button.data('type');
|
||||
const action = $('#bulk-plugin-form').is(':visible') ? 'bpi_install_plugins' : 'bpi_install_themes';
|
||||
const $li = $button.closest('li');
|
||||
$li.find('.spinner').addClass('is-active');
|
||||
$li.find('.status').html('');
|
||||
|
||||
$.ajax({
|
||||
url: bpiAjax.ajaxurl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: action,
|
||||
nonce: bpiAjax.nonce,
|
||||
items: JSON.stringify([item]),
|
||||
install_type: type
|
||||
},
|
||||
success: function(response) {
|
||||
console.log('Retry response:', response); // 调试输出
|
||||
$li.find('.spinner').removeClass('is-active');
|
||||
if (response.success && response.data[item]) {
|
||||
const result = response.data[item];
|
||||
$li.removeClass('error success').addClass(result.success ? 'success' : 'error');
|
||||
let statusHtml = '';
|
||||
if (result.success) {
|
||||
statusHtml = result.skipped ? 'ⓘ ' + escapeHtml(result.message) : '✓ ' + escapeHtml(result.message);
|
||||
} else {
|
||||
statusHtml = '✗ ' + escapeHtml(result.message);
|
||||
if (result.retry) {
|
||||
statusHtml += ' <button class="retry-btn" data-item="' + escapeHtml(item) + '" data-type="' + type + '">Retry</button>';
|
||||
}
|
||||
}
|
||||
$li.find('.status').html(statusHtml);
|
||||
} else {
|
||||
$li.addClass('error')
|
||||
.find('.status')
|
||||
.html('✗ ' + escapeHtml(response.data || 'Unknown error') + ' <button class="retry-btn" data-item="' + escapeHtml(item) + '" data-type="' + type + '">Retry</button>');
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.log('Retry error:', xhr, status, error); // 调试输出
|
||||
$li.find('.spinner').removeClass('is-active')
|
||||
.addClass('error')
|
||||
.find('.status')
|
||||
.html(`✗ ${escapeHtml(xhr.responseText || 'Retry failed: ' + error)} <button class="retry-btn" data-item="${escapeHtml(item)}" data-type="${type}">Retry</button>`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#bpi-settings-form').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const $form = $(this);
|
||||
|
@ -224,8 +291,6 @@ jQuery(document).ready(function($) {
|
|||
$submitButton.prop('disabled', true).text('Saving...');
|
||||
$status.removeClass('notice-success notice-error').addClass('notice-info').text('Saving...').show();
|
||||
|
||||
const formData = $form.serialize(); // 使用 serialize() 而不是 serializeArray()
|
||||
|
||||
$.ajax({
|
||||
url: bpiAjax.ajaxurl,
|
||||
type: 'POST',
|
|
@ -22,8 +22,8 @@ define('BPI_VERSION', '1.1.6');
|
|||
define('BPI_PATH', plugin_dir_path(__FILE__));
|
||||
define('BPI_URL', plugin_dir_url(__FILE__));
|
||||
|
||||
require_once BPI_PATH . 'class-installer.php';
|
||||
require_once BPI_PATH . 'admin-page.php';
|
||||
require_once BPI_PATH . 'includes/class-installer.php';
|
||||
require_once BPI_PATH . 'includes/admin-page.php';
|
||||
|
||||
function bpi_init() {
|
||||
if (is_multisite()) {
|
||||
|
|
|
@ -4,8 +4,8 @@ function bpi_render_admin_page() {
|
|||
wp_die(__('You do not have sufficient permissions to access this page.', 'bulk-plugin-installer'));
|
||||
}
|
||||
|
||||
wp_enqueue_style('bpi-admin-style', BPI_URL . 'css/admin.css', [], BPI_VERSION);
|
||||
wp_enqueue_script('bpi-admin', BPI_URL . 'js/admin.js', ['jquery'], BPI_VERSION, true);
|
||||
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);
|
||||
wp_localize_script('bpi-admin', 'bpiAjax', [
|
||||
'nonce' => wp_create_nonce('bpi_installer'),
|
||||
'ajaxurl' => admin_url('admin-ajax.php')
|
|
@ -7,7 +7,7 @@ class BPI_Installer {
|
|||
if (empty($wp_filesystem)) {
|
||||
require_once ABSPATH . '/wp-admin/includes/file.php';
|
||||
if (!WP_Filesystem()) {
|
||||
throw new Exception(__('Unable to initialize filesystem. Please check server permissions.', 'bulk-plugin-installer'));
|
||||
throw new Exception('Unable to initialize filesystem. Please check server permissions.');
|
||||
}
|
||||
}
|
||||
$this->wp_filesystem = $wp_filesystem;
|
||||
|
@ -48,11 +48,11 @@ class BPI_Installer {
|
|||
public function bpi_install_plugins($items, $type) {
|
||||
$valid_types = ['repository', 'wenpai', 'url', 'upload'];
|
||||
if (!in_array($type, $valid_types)) {
|
||||
return ['error' => __('Invalid installation type', 'bulk-plugin-installer')];
|
||||
return ['error' => 'Invalid installation type'];
|
||||
}
|
||||
|
||||
if (empty($items) || !is_array($items)) {
|
||||
return ['error' => __('No items provided', 'bulk-plugin-installer')];
|
||||
return ['error' => 'No items provided'];
|
||||
}
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
|
||||
|
@ -69,7 +69,7 @@ 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', 'bulk-plugin-installer'));
|
||||
throw new Exception('Invalid plugin slug', 1001);
|
||||
}
|
||||
$plugin_key = $item . '/' . $item . '.php';
|
||||
} elseif ($type === 'upload') {
|
||||
|
@ -80,11 +80,13 @@ class BPI_Installer {
|
|||
if ($plugin_key && array_key_exists($plugin_key, $installed_plugins)) {
|
||||
$results[$item] = [
|
||||
'success' => true,
|
||||
'message' => __('Plugin already installed, skipped', 'bulk-plugin-installer')
|
||||
'message' => 'Plugin already installed, skipped',
|
||||
'skipped' => true
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
$download_link = '';
|
||||
switch ($type) {
|
||||
case 'repository':
|
||||
$api = plugins_api('plugin_information', [
|
||||
|
@ -92,78 +94,88 @@ class BPI_Installer {
|
|||
'fields' => ['sections' => false]
|
||||
]);
|
||||
if (is_wp_error($api)) {
|
||||
throw new Exception($api->get_error_message());
|
||||
throw new Exception('Failed to fetch plugin info: ' . $api->get_error_message(), 1002);
|
||||
}
|
||||
$result = $upgrader->install($api->download_link);
|
||||
$download_link = $api->download_link;
|
||||
break;
|
||||
|
||||
case 'wenpai':
|
||||
$response = wp_remote_get("https://api.wenpai.net/wp-json/wp/v2/plugins/{$item}");
|
||||
if (is_wp_error($response)) {
|
||||
throw new Exception($response->get_error_message());
|
||||
$code = wp_remote_retrieve_response_code($response);
|
||||
throw new Exception("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'])
|
||||
? $plugin_data['download_link']
|
||||
: "https://downloads.wenpai.net/plugin/{$item}.latest-stable.zip";
|
||||
$result = $upgrader->install($download_link);
|
||||
break;
|
||||
|
||||
case 'url':
|
||||
$item = sanitize_text_field($item);
|
||||
if (!filter_var($item, FILTER_VALIDATE_URL) || !bpi_is_domain_allowed($item)) {
|
||||
throw new Exception(__('Invalid or untrusted URL', 'bulk-plugin-installer'));
|
||||
if (!filter_var($item, FILTER_VALIDATE_URL)) {
|
||||
throw new Exception('Invalid URL format', 1004);
|
||||
}
|
||||
$result = $upgrader->install($item);
|
||||
if (!bpi_is_domain_allowed($item)) {
|
||||
throw new Exception('Untrusted domain', 1005);
|
||||
}
|
||||
$download_link = $item;
|
||||
break;
|
||||
|
||||
case 'upload':
|
||||
$file = $item;
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||
throw new Exception(__('File upload error: ', 'bulk-plugin-installer') . $file['error']);
|
||||
throw new Exception('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', 'bulk-plugin-installer'));
|
||||
throw new Exception('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.', 'bulk-plugin-installer'));
|
||||
throw new Exception('Failed to move uploaded file. Check server permissions.', 1008);
|
||||
}
|
||||
|
||||
// 检查 ZIP 文件类型
|
||||
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 Themes tab to install.', 'bulk-plugin-installer'));
|
||||
throw new Exception('This appears to be a theme ZIP. Please use the Plugins tab to install.', 1009);
|
||||
}
|
||||
unlink($dest_path);
|
||||
throw new Exception(__('Invalid plugin ZIP file', 'bulk-plugin-installer'));
|
||||
throw new Exception('Invalid plugin ZIP file', 1010);
|
||||
}
|
||||
|
||||
$result = $upgrader->install($dest_path);
|
||||
if (file_exists($dest_path)) {
|
||||
unlink($dest_path);
|
||||
}
|
||||
$download_link = $dest_path;
|
||||
$item = $file_name;
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $upgrader->install($download_link);
|
||||
if ($type === 'upload' && file_exists($dest_path)) {
|
||||
unlink($dest_path);
|
||||
}
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
throw new Exception($result->get_error_message());
|
||||
$error_message = $result->get_error_message();
|
||||
throw new Exception($error_message ?: 'Unknown installation error', 1011);
|
||||
} elseif ($result !== true) {
|
||||
throw new Exception('Installation failed unexpectedly', 1012);
|
||||
}
|
||||
|
||||
$results[$item] = [
|
||||
'success' => $result === true,
|
||||
'message' => $result === true ? __('Successfully installed', 'bulk-plugin-installer') : __('Installation failed', 'bulk-plugin-installer')
|
||||
'success' => true,
|
||||
'message' => 'Successfully installed'
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
$no_retry_codes = [1001, 1004, 1005, 1007, 1009, 1010]; // 无需重试的错误代码
|
||||
$results[$item] = [
|
||||
'success' => false,
|
||||
'message' => $e->getMessage()
|
||||
'message' => $e->getMessage(),
|
||||
'error_code' => $e->getCode(),
|
||||
'retry' => !in_array($e->getCode(), $no_retry_codes)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -174,11 +186,11 @@ class BPI_Installer {
|
|||
public function bpi_install_themes($items, $type) {
|
||||
$valid_types = ['repository', 'wenpai', 'url', 'upload'];
|
||||
if (!in_array($type, $valid_types)) {
|
||||
return ['error' => __('Invalid installation type', 'bulk-plugin-installer')];
|
||||
return ['error' => 'Invalid installation type'];
|
||||
}
|
||||
|
||||
if (empty($items) || !is_array($items)) {
|
||||
return ['error' => __('No items provided', 'bulk-plugin-installer')];
|
||||
return ['error' => 'No items provided'];
|
||||
}
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/theme-install.php';
|
||||
|
@ -191,23 +203,79 @@ class BPI_Installer {
|
|||
foreach ($items as $item) {
|
||||
try {
|
||||
$theme_key = null;
|
||||
$download_link = '';
|
||||
if ($type === 'repository' || $type === 'wenpai') {
|
||||
$item = sanitize_text_field($item);
|
||||
if (!preg_match('/^[a-z0-9-]+$/', $item)) {
|
||||
throw new Exception(__('Invalid theme slug', 'bulk-plugin-installer'));
|
||||
throw new Exception('Invalid theme slug', 2001);
|
||||
}
|
||||
$theme_key = $item;
|
||||
} elseif ($type === 'upload') {
|
||||
$file_name = sanitize_file_name($item['name']);
|
||||
$theme_key = pathinfo($file_name, PATHINFO_FILENAME);
|
||||
}
|
||||
$file = $item;
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||
throw new Exception('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);
|
||||
}
|
||||
$temp_file = $file['tmp_name'];
|
||||
$upload_dir = wp_upload_dir();
|
||||
$dest_path = $upload_dir['path'] . '/' . $file_name;
|
||||
|
||||
if ($theme_key && array_key_exists($theme_key, $installed_themes)) {
|
||||
$results[$item] = [
|
||||
'success' => true,
|
||||
'message' => __('Theme already installed, skipped', 'bulk-plugin-installer')
|
||||
];
|
||||
continue;
|
||||
if (!move_uploaded_file($temp_file, $dest_path)) {
|
||||
throw new Exception('Failed to move uploaded file. Check server permissions.', 2004);
|
||||
}
|
||||
|
||||
$zip = new ZipArchive();
|
||||
if ($zip->open($dest_path) === true) {
|
||||
$theme_key = null;
|
||||
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||
$filename = $zip->getNameIndex($i);
|
||||
if (strpos($filename, 'style.css') !== false) {
|
||||
$theme_key = dirname($filename);
|
||||
if ($theme_key === '.') {
|
||||
$theme_key = pathinfo($file_name, PATHINFO_FILENAME);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
$zip->close();
|
||||
}
|
||||
|
||||
if ($theme_key && array_key_exists($theme_key, $installed_themes)) {
|
||||
if (file_exists($dest_path)) {
|
||||
unlink($dest_path);
|
||||
}
|
||||
$results[$file_name] = [
|
||||
'success' => true,
|
||||
'message' => 'Theme already installed, skipped',
|
||||
'skipped' => true
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
unlink($dest_path);
|
||||
throw new Exception('Invalid theme ZIP file', 2006);
|
||||
}
|
||||
|
||||
$download_link = $dest_path;
|
||||
$item = $file_name;
|
||||
} else {
|
||||
$theme_key = $item;
|
||||
if ($theme_key && array_key_exists($theme_key, $installed_themes)) {
|
||||
$results[$item] = [
|
||||
'success' => true,
|
||||
'message' => 'Theme already installed, skipped',
|
||||
'skipped' => true
|
||||
];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
|
@ -217,78 +285,62 @@ class BPI_Installer {
|
|||
'fields' => ['sections' => false]
|
||||
]);
|
||||
if (is_wp_error($api)) {
|
||||
throw new Exception($api->get_error_message());
|
||||
throw new Exception('Failed to fetch theme info: ' . $api->get_error_message(), 2007);
|
||||
}
|
||||
$result = $upgrader->install($api->download_link);
|
||||
$download_link = $api->download_link;
|
||||
break;
|
||||
|
||||
case 'wenpai':
|
||||
$response = wp_remote_get("https://api.wenpai.net/wp-json/wp/v2/themes/{$item}");
|
||||
if (is_wp_error($response)) {
|
||||
throw new Exception($response->get_error_message());
|
||||
$code = wp_remote_retrieve_response_code($response);
|
||||
throw new Exception("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'])
|
||||
? $theme_data['download_link']
|
||||
: "https://downloads.wenpai.net/theme/{$item}.latest-stable.zip";
|
||||
$result = $upgrader->install($download_link);
|
||||
break;
|
||||
|
||||
case 'url':
|
||||
$item = sanitize_text_field($item);
|
||||
if (!filter_var($item, FILTER_VALIDATE_URL) || !bpi_is_domain_allowed($item)) {
|
||||
throw new Exception(__('Invalid or untrusted URL', 'bulk-plugin-installer'));
|
||||
if (!filter_var($item, FILTER_VALIDATE_URL)) {
|
||||
throw new Exception('Invalid URL format', 2009);
|
||||
}
|
||||
$result = $upgrader->install($item);
|
||||
if (!bpi_is_domain_allowed($item)) {
|
||||
throw new Exception('Untrusted domain', 2010);
|
||||
}
|
||||
$download_link = $item;
|
||||
break;
|
||||
|
||||
case 'upload':
|
||||
$file = $item;
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||
throw new Exception(__('File upload error: ', 'bulk-plugin-installer') . $file['error']);
|
||||
}
|
||||
$file_name = sanitize_file_name($file['name']);
|
||||
if (pathinfo($file_name, PATHINFO_EXTENSION) !== 'zip') {
|
||||
throw new Exception(__('Only ZIP files are allowed', 'bulk-plugin-installer'));
|
||||
}
|
||||
$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.', 'bulk-plugin-installer'));
|
||||
}
|
||||
|
||||
// 检查 ZIP 文件类型
|
||||
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.', 'bulk-plugin-installer'));
|
||||
}
|
||||
unlink($dest_path);
|
||||
throw new Exception(__('Invalid theme ZIP file', 'bulk-plugin-installer'));
|
||||
}
|
||||
|
||||
$result = $upgrader->install($dest_path);
|
||||
if (file_exists($dest_path)) {
|
||||
unlink($dest_path);
|
||||
}
|
||||
$item = $file_name;
|
||||
// 已在上方处理
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $upgrader->install($download_link);
|
||||
if ($type === 'upload' && file_exists($dest_path)) {
|
||||
unlink($dest_path);
|
||||
}
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
throw new Exception($result->get_error_message());
|
||||
$error_message = $result->get_error_message();
|
||||
throw new Exception($error_message ?: 'Unknown installation error', 2011);
|
||||
} elseif ($result !== true) {
|
||||
throw new Exception('Installation failed unexpectedly', 2012);
|
||||
}
|
||||
|
||||
$results[$item] = [
|
||||
'success' => $result === true,
|
||||
'message' => $result === true ? __('Successfully installed', 'bulk-plugin-installer') : __('Installation failed', 'bulk-plugin-installer')
|
||||
'success' => true,
|
||||
'message' => 'Successfully installed'
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
$no_retry_codes = [2001, 2003, 2005, 2006, 2009, 2010]; // 无需重试的错误代码
|
||||
$results[$item] = [
|
||||
'success' => false,
|
||||
'message' => $e->getMessage()
|
||||
'message' => $e->getMessage(),
|
||||
'error_code' => $e->getCode(),
|
||||
'retry' => !in_array($e->getCode(), $no_retry_codes)
|
||||
];
|
||||
}
|
||||
}
|
31
installer-server.json
Normal file
31
installer-server.json
Normal file
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"plugins": {
|
||||
"repository": [
|
||||
"woocommerce",
|
||||
"wordpress-seo",
|
||||
"elementor"
|
||||
],
|
||||
"wenpai": [
|
||||
"wpfanyi-import",
|
||||
"wpavatar"
|
||||
],
|
||||
"url": [
|
||||
"https://downloads.wenpai.net/plugin/custom-plugin.zip",
|
||||
"https://github.com/user/plugin/releases/download/v1.0/plugin.zip"
|
||||
]
|
||||
},
|
||||
"themes": {
|
||||
"repository": [
|
||||
"twentytwentyfive",
|
||||
"astra"
|
||||
],
|
||||
"wenpai": [
|
||||
"weicommerce",
|
||||
"justnote"
|
||||
],
|
||||
"url": [
|
||||
"https://downloads.wenpai.net/theme/custom-theme.zip",
|
||||
"https://github.com/user/theme/releases/download/v1.0/theme.zip"
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue