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
|
@ -192,4 +192,19 @@
|
||||||
background: #f8f9fa;
|
background: #f8f9fa;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin: 10px 0;
|
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($) {
|
jQuery(document).ready(function($) {
|
||||||
|
const $results = $('#installation-results');
|
||||||
|
|
||||||
$('.bpi-tab').on('click', function() {
|
$('.bpi-tab').on('click', function() {
|
||||||
$('.bpi-tab').removeClass('active');
|
$('.bpi-tab').removeClass('active');
|
||||||
$(this).addClass('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 action = $form.attr('id') === 'bulk-plugin-form' ? 'bpi_install_plugins' : 'bpi_install_themes';
|
||||||
const type = $form.find('.bpi-select').val();
|
const type = $form.find('.bpi-select').val();
|
||||||
const $submitButton = $form.find('button[type="submit"]');
|
const $submitButton = $form.find('button[type="submit"]');
|
||||||
const $results = $('#installation-results');
|
|
||||||
|
|
||||||
let items = [];
|
let items = [];
|
||||||
let errorMessage = '';
|
let errorMessage = '';
|
||||||
|
@ -132,17 +133,19 @@ jQuery(document).ready(function($) {
|
||||||
processData: false,
|
processData: false,
|
||||||
contentType: false,
|
contentType: false,
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
|
console.log('Upload response:', response); // 调试输出
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
Object.keys(response.data).forEach((item, index) => {
|
Object.keys(response.data).forEach((item, index) => {
|
||||||
handleResponse(response, item, index);
|
handleResponse(response, item, index);
|
||||||
});
|
});
|
||||||
} else {
|
} 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();
|
installationComplete();
|
||||||
},
|
},
|
||||||
error: function(xhr, status, error) {
|
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();
|
installationComplete();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -169,10 +172,12 @@ jQuery(document).ready(function($) {
|
||||||
install_type: type
|
install_type: type
|
||||||
},
|
},
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
|
console.log('Item response:', response); // 调试输出
|
||||||
handleResponse(response, item, index);
|
handleResponse(response, item, index);
|
||||||
processNextItem(index + 1);
|
processNextItem(index + 1);
|
||||||
},
|
},
|
||||||
error: function(xhr, status, error) {
|
error: function(xhr, status, error) {
|
||||||
|
console.log('Item error:', xhr, status, error); // 调试输出
|
||||||
handleError(xhr, status, error, item, index);
|
handleError(xhr, status, error, item, index);
|
||||||
processNextItem(index + 1);
|
processNextItem(index + 1);
|
||||||
}
|
}
|
||||||
|
@ -183,12 +188,23 @@ jQuery(document).ready(function($) {
|
||||||
const $item = $(`#item-${index}`) || $list.find('li:last');
|
const $item = $(`#item-${index}`) || $list.find('li:last');
|
||||||
$item.find('.spinner').removeClass('is-active');
|
$item.find('.spinner').removeClass('is-active');
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success && response.data[item]) {
|
||||||
const result = response.data[item];
|
const result = response.data[item];
|
||||||
$item.addClass(result.success ? 'success' : 'error')
|
$item.addClass(result.success ? 'success' : 'error');
|
||||||
.find('.status').text(result.success ? '✓ ' + result.message : '✗ ' + result.message);
|
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 {
|
} 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++;
|
completed++;
|
||||||
|
@ -201,7 +217,8 @@ jQuery(document).ready(function($) {
|
||||||
const $item = $(`#item-${index}`) || $list.find('li:last');
|
const $item = $(`#item-${index}`) || $list.find('li:last');
|
||||||
$item.find('.spinner').removeClass('is-active')
|
$item.find('.spinner').removeClass('is-active')
|
||||||
.addClass('error')
|
.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++;
|
completed++;
|
||||||
const percentage = Math.round((completed / items.length) * 100);
|
const percentage = Math.round((completed / items.length) * 100);
|
||||||
const remaining = items.length - completed;
|
const remaining = items.length - completed;
|
||||||
|
@ -211,10 +228,60 @@ jQuery(document).ready(function($) {
|
||||||
function installationComplete() {
|
function installationComplete() {
|
||||||
$submitButton.prop('disabled', false).text(`Install ${action === 'bpi_install_plugins' ? 'Plugins' : 'Themes'}`);
|
$submitButton.prop('disabled', false).text(`Install ${action === 'bpi_install_plugins' ? 'Plugins' : 'Themes'}`);
|
||||||
const $notice = $results.find('.notice').removeClass('notice-info').addClass('notice-success');
|
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) {
|
$('#bpi-settings-form').on('submit', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const $form = $(this);
|
const $form = $(this);
|
||||||
|
@ -224,8 +291,6 @@ jQuery(document).ready(function($) {
|
||||||
$submitButton.prop('disabled', true).text('Saving...');
|
$submitButton.prop('disabled', true).text('Saving...');
|
||||||
$status.removeClass('notice-success notice-error').addClass('notice-info').text('Saving...').show();
|
$status.removeClass('notice-success notice-error').addClass('notice-info').text('Saving...').show();
|
||||||
|
|
||||||
const formData = $form.serialize(); // 使用 serialize() 而不是 serializeArray()
|
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: bpiAjax.ajaxurl,
|
url: bpiAjax.ajaxurl,
|
||||||
type: 'POST',
|
type: 'POST',
|
|
@ -22,8 +22,8 @@ define('BPI_VERSION', '1.1.6');
|
||||||
define('BPI_PATH', plugin_dir_path(__FILE__));
|
define('BPI_PATH', plugin_dir_path(__FILE__));
|
||||||
define('BPI_URL', plugin_dir_url(__FILE__));
|
define('BPI_URL', plugin_dir_url(__FILE__));
|
||||||
|
|
||||||
require_once BPI_PATH . 'class-installer.php';
|
require_once BPI_PATH . 'includes/class-installer.php';
|
||||||
require_once BPI_PATH . 'admin-page.php';
|
require_once BPI_PATH . 'includes/admin-page.php';
|
||||||
|
|
||||||
function bpi_init() {
|
function bpi_init() {
|
||||||
if (is_multisite()) {
|
if (is_multisite()) {
|
||||||
|
@ -53,10 +53,10 @@ function bpi_add_menu_page() {
|
||||||
|
|
||||||
function bpi_add_network_submenu_page() {
|
function bpi_add_network_submenu_page() {
|
||||||
add_submenu_page(
|
add_submenu_page(
|
||||||
'plugins.php',
|
'plugins.php',
|
||||||
__('Plugin Installer', 'bulk-plugin-installer'),
|
__('Plugin Installer', 'bulk-plugin-installer'),
|
||||||
__('Plugin Installer', 'bulk-plugin-installer'),
|
__('Plugin Installer', 'bulk-plugin-installer'),
|
||||||
'manage_network_plugins',
|
'manage_network_plugins',
|
||||||
'bulk-plugin-installer',
|
'bulk-plugin-installer',
|
||||||
'bpi_render_admin_page'
|
'bpi_render_admin_page'
|
||||||
);
|
);
|
||||||
|
@ -69,7 +69,7 @@ define('BPI_TRUSTED_DOMAINS', [
|
||||||
'github.com',
|
'github.com',
|
||||||
'raw.githubusercontent.com',
|
'raw.githubusercontent.com',
|
||||||
'wenpai.cn',
|
'wenpai.cn',
|
||||||
'wenpai.net',
|
'wenpai.net',
|
||||||
'wenpai.org',
|
'wenpai.org',
|
||||||
'downloads.wenpai.net',
|
'downloads.wenpai.net',
|
||||||
'weixiaoduo.com',
|
'weixiaoduo.com',
|
||||||
|
@ -141,7 +141,7 @@ function bpi_is_domain_allowed($url) {
|
||||||
|
|
||||||
function bpi_handle_install_plugins() {
|
function bpi_handle_install_plugins() {
|
||||||
check_ajax_referer('bpi_installer', 'nonce');
|
check_ajax_referer('bpi_installer', 'nonce');
|
||||||
|
|
||||||
if (!current_user_can('install_plugins') && !(is_multisite() && current_user_can('manage_network_plugins'))) {
|
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(__('Insufficient permissions', 'bulk-plugin-installer'));
|
||||||
}
|
}
|
||||||
|
@ -201,7 +201,7 @@ function bpi_handle_install_plugins() {
|
||||||
|
|
||||||
function bpi_handle_install_themes() {
|
function bpi_handle_install_themes() {
|
||||||
check_ajax_referer('bpi_installer', 'nonce');
|
check_ajax_referer('bpi_installer', 'nonce');
|
||||||
|
|
||||||
if (!current_user_can('install_themes') && !(is_multisite() && current_user_can('manage_network_plugins'))) {
|
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(__('Insufficient permissions', 'bulk-plugin-installer'));
|
||||||
}
|
}
|
||||||
|
@ -322,4 +322,4 @@ function bpi_activate() {
|
||||||
'last_install_time' => ''
|
'last_install_time' => ''
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,9 @@ function bpi_render_admin_page() {
|
||||||
if (!bpi_user_can_install()) {
|
if (!bpi_user_can_install()) {
|
||||||
wp_die(__('You do not have sufficient permissions to access this page.', 'bulk-plugin-installer'));
|
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_style('bpi-admin-style', BPI_URL . 'assets/css/admin.css', [], BPI_VERSION);
|
||||||
wp_enqueue_script('bpi-admin', BPI_URL . 'js/admin.js', ['jquery'], BPI_VERSION, true);
|
wp_enqueue_script('bpi-admin', BPI_URL . 'assets/js/admin.js', ['jquery'], BPI_VERSION, true);
|
||||||
wp_localize_script('bpi-admin', 'bpiAjax', [
|
wp_localize_script('bpi-admin', 'bpiAjax', [
|
||||||
'nonce' => wp_create_nonce('bpi_installer'),
|
'nonce' => wp_create_nonce('bpi_installer'),
|
||||||
'ajaxurl' => admin_url('admin-ajax.php')
|
'ajaxurl' => admin_url('admin-ajax.php')
|
||||||
|
@ -43,21 +43,21 @@ function bpi_render_admin_page() {
|
||||||
|
|
||||||
<div class="bpi-form-row source-input repository-source active">
|
<div class="bpi-form-row source-input repository-source active">
|
||||||
<label for="plugin-slugs"><?php _e('Plugin Slugs:', 'bulk-plugin-installer'); ?></label>
|
<label for="plugin-slugs"><?php _e('Plugin Slugs:', 'bulk-plugin-installer'); ?></label>
|
||||||
<textarea id="plugin-slugs" name="items" rows="8"
|
<textarea id="plugin-slugs" name="items" rows="8"
|
||||||
placeholder="<?php esc_attr_e('Enter plugin slugs, one per line (e.g., akismet)', 'bulk-plugin-installer'); ?>"
|
placeholder="<?php esc_attr_e('Enter plugin slugs, one per line (e.g., akismet)', 'bulk-plugin-installer'); ?>"
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bpi-form-row source-input wenpai-source">
|
<div class="bpi-form-row source-input wenpai-source">
|
||||||
<label for="plugin-wenpai-slugs"><?php _e('Plugin Slugs (WenPai.org):', 'bulk-plugin-installer'); ?></label>
|
<label for="plugin-wenpai-slugs"><?php _e('Plugin Slugs (WenPai.org):', 'bulk-plugin-installer'); ?></label>
|
||||||
<textarea id="plugin-wenpai-slugs" name="items" rows="8"
|
<textarea id="plugin-wenpai-slugs" name="items" rows="8"
|
||||||
placeholder="<?php esc_attr_e('Enter plugin slugs, one per line (e.g., akismet)', 'bulk-plugin-installer'); ?>"
|
placeholder="<?php esc_attr_e('Enter plugin slugs, one per line (e.g., akismet)', 'bulk-plugin-installer'); ?>"
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bpi-form-row source-input url-source">
|
<div class="bpi-form-row source-input url-source">
|
||||||
<label for="plugin-urls"><?php _e('Download URLs:', 'bulk-plugin-installer'); ?></label>
|
<label for="plugin-urls"><?php _e('Download URLs:', 'bulk-plugin-installer'); ?></label>
|
||||||
<textarea id="plugin-urls" name="items" rows="8"
|
<textarea id="plugin-urls" name="items" rows="8"
|
||||||
placeholder="<?php esc_attr_e('Enter download URLs, one per line', 'bulk-plugin-installer'); ?>"
|
placeholder="<?php esc_attr_e('Enter download URLs, one per line', 'bulk-plugin-installer'); ?>"
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
@ -98,21 +98,21 @@ function bpi_render_admin_page() {
|
||||||
|
|
||||||
<div class="bpi-form-row source-input repository-source active">
|
<div class="bpi-form-row source-input repository-source active">
|
||||||
<label for="theme-slugs"><?php _e('Theme Slugs:', 'bulk-plugin-installer'); ?></label>
|
<label for="theme-slugs"><?php _e('Theme Slugs:', 'bulk-plugin-installer'); ?></label>
|
||||||
<textarea id="theme-slugs" name="items" rows="8"
|
<textarea id="theme-slugs" name="items" rows="8"
|
||||||
placeholder="<?php esc_attr_e('Enter theme slugs, one per line (e.g., twentytwenty)', 'bulk-plugin-installer'); ?>"
|
placeholder="<?php esc_attr_e('Enter theme slugs, one per line (e.g., twentytwenty)', 'bulk-plugin-installer'); ?>"
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bpi-form-row source-input wenpai-source">
|
<div class="bpi-form-row source-input wenpai-source">
|
||||||
<label for="theme-wenpai-slugs"><?php _e('Theme Slugs (WenPai.org):', 'bulk-plugin-installer'); ?></label>
|
<label for="theme-wenpai-slugs"><?php _e('Theme Slugs (WenPai.org):', 'bulk-plugin-installer'); ?></label>
|
||||||
<textarea id="theme-wenpai-slugs" name="items" rows="8"
|
<textarea id="theme-wenpai-slugs" name="items" rows="8"
|
||||||
placeholder="<?php esc_attr_e('Enter theme slugs, one per line (e.g., twentytwenty)', 'bulk-plugin-installer'); ?>"
|
placeholder="<?php esc_attr_e('Enter theme slugs, one per line (e.g., twentytwenty)', 'bulk-plugin-installer'); ?>"
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bpi-form-row source-input url-source">
|
<div class="bpi-form-row source-input url-source">
|
||||||
<label for="theme-urls"><?php _e('Download URLs:', 'bulk-plugin-installer'); ?></label>
|
<label for="theme-urls"><?php _e('Download URLs:', 'bulk-plugin-installer'); ?></label>
|
||||||
<textarea id="theme-urls" name="items" rows="8"
|
<textarea id="theme-urls" name="items" rows="8"
|
||||||
placeholder="<?php esc_attr_e('Enter download URLs, one per line', 'bulk-plugin-installer'); ?>"
|
placeholder="<?php esc_attr_e('Enter download URLs, one per line', 'bulk-plugin-installer'); ?>"
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
@ -222,4 +222,4 @@ function bpi_render_admin_page() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php
|
<?php
|
||||||
}
|
}
|
|
@ -7,7 +7,7 @@ class BPI_Installer {
|
||||||
if (empty($wp_filesystem)) {
|
if (empty($wp_filesystem)) {
|
||||||
require_once ABSPATH . '/wp-admin/includes/file.php';
|
require_once ABSPATH . '/wp-admin/includes/file.php';
|
||||||
if (!WP_Filesystem()) {
|
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;
|
$this->wp_filesystem = $wp_filesystem;
|
||||||
|
@ -48,11 +48,11 @@ class BPI_Installer {
|
||||||
public function bpi_install_plugins($items, $type) {
|
public function bpi_install_plugins($items, $type) {
|
||||||
$valid_types = ['repository', 'wenpai', 'url', 'upload'];
|
$valid_types = ['repository', 'wenpai', 'url', 'upload'];
|
||||||
if (!in_array($type, $valid_types)) {
|
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)) {
|
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';
|
require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
|
||||||
|
@ -69,7 +69,7 @@ class BPI_Installer {
|
||||||
if ($type === 'repository' || $type === 'wenpai') {
|
if ($type === 'repository' || $type === 'wenpai') {
|
||||||
$item = sanitize_text_field($item);
|
$item = sanitize_text_field($item);
|
||||||
if (!preg_match('/^[a-z0-9-]+$/', $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';
|
$plugin_key = $item . '/' . $item . '.php';
|
||||||
} elseif ($type === 'upload') {
|
} elseif ($type === 'upload') {
|
||||||
|
@ -80,11 +80,13 @@ class BPI_Installer {
|
||||||
if ($plugin_key && array_key_exists($plugin_key, $installed_plugins)) {
|
if ($plugin_key && array_key_exists($plugin_key, $installed_plugins)) {
|
||||||
$results[$item] = [
|
$results[$item] = [
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'message' => __('Plugin already installed, skipped', 'bulk-plugin-installer')
|
'message' => 'Plugin already installed, skipped',
|
||||||
|
'skipped' => true
|
||||||
];
|
];
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$download_link = '';
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
case 'repository':
|
case 'repository':
|
||||||
$api = plugins_api('plugin_information', [
|
$api = plugins_api('plugin_information', [
|
||||||
|
@ -92,78 +94,88 @@ class BPI_Installer {
|
||||||
'fields' => ['sections' => false]
|
'fields' => ['sections' => false]
|
||||||
]);
|
]);
|
||||||
if (is_wp_error($api)) {
|
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;
|
break;
|
||||||
|
|
||||||
case 'wenpai':
|
case 'wenpai':
|
||||||
$response = wp_remote_get("https://api.wenpai.net/wp-json/wp/v2/plugins/{$item}");
|
$response = wp_remote_get("https://api.wenpai.net/wp-json/wp/v2/plugins/{$item}");
|
||||||
if (is_wp_error($response)) {
|
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);
|
$plugin_data = json_decode(wp_remote_retrieve_body($response), true);
|
||||||
$download_link = $plugin_data && !empty($plugin_data['download_link'])
|
$download_link = $plugin_data && !empty($plugin_data['download_link'])
|
||||||
? $plugin_data['download_link']
|
? $plugin_data['download_link']
|
||||||
: "https://downloads.wenpai.net/plugin/{$item}.latest-stable.zip";
|
: "https://downloads.wenpai.net/plugin/{$item}.latest-stable.zip";
|
||||||
$result = $upgrader->install($download_link);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'url':
|
case 'url':
|
||||||
$item = sanitize_text_field($item);
|
$item = sanitize_text_field($item);
|
||||||
if (!filter_var($item, FILTER_VALIDATE_URL) || !bpi_is_domain_allowed($item)) {
|
if (!filter_var($item, FILTER_VALIDATE_URL)) {
|
||||||
throw new Exception(__('Invalid or untrusted URL', 'bulk-plugin-installer'));
|
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;
|
break;
|
||||||
|
|
||||||
case 'upload':
|
case 'upload':
|
||||||
$file = $item;
|
$file = $item;
|
||||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
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']);
|
$file_name = sanitize_file_name($file['name']);
|
||||||
if (pathinfo($file_name, PATHINFO_EXTENSION) !== 'zip') {
|
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'];
|
$temp_file = $file['tmp_name'];
|
||||||
$upload_dir = wp_upload_dir();
|
$upload_dir = wp_upload_dir();
|
||||||
$dest_path = $upload_dir['path'] . '/' . $file_name;
|
$dest_path = $upload_dir['path'] . '/' . $file_name;
|
||||||
|
|
||||||
if (!move_uploaded_file($temp_file, $dest_path)) {
|
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_plugin_zip($dest_path)) {
|
||||||
if ($this->is_theme_zip($dest_path)) {
|
if ($this->is_theme_zip($dest_path)) {
|
||||||
unlink($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);
|
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);
|
$download_link = $dest_path;
|
||||||
if (file_exists($dest_path)) {
|
|
||||||
unlink($dest_path);
|
|
||||||
}
|
|
||||||
$item = $file_name;
|
$item = $file_name;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$result = $upgrader->install($download_link);
|
||||||
|
if ($type === 'upload' && file_exists($dest_path)) {
|
||||||
|
unlink($dest_path);
|
||||||
|
}
|
||||||
|
|
||||||
if (is_wp_error($result)) {
|
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] = [
|
$results[$item] = [
|
||||||
'success' => $result === true,
|
'success' => true,
|
||||||
'message' => $result === true ? __('Successfully installed', 'bulk-plugin-installer') : __('Installation failed', 'bulk-plugin-installer')
|
'message' => 'Successfully installed'
|
||||||
];
|
];
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
$no_retry_codes = [1001, 1004, 1005, 1007, 1009, 1010]; // 无需重试的错误代码
|
||||||
$results[$item] = [
|
$results[$item] = [
|
||||||
'success' => false,
|
'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) {
|
public function bpi_install_themes($items, $type) {
|
||||||
$valid_types = ['repository', 'wenpai', 'url', 'upload'];
|
$valid_types = ['repository', 'wenpai', 'url', 'upload'];
|
||||||
if (!in_array($type, $valid_types)) {
|
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)) {
|
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';
|
require_once ABSPATH . 'wp-admin/includes/theme-install.php';
|
||||||
|
@ -191,23 +203,79 @@ class BPI_Installer {
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
try {
|
try {
|
||||||
$theme_key = null;
|
$theme_key = null;
|
||||||
|
$download_link = '';
|
||||||
if ($type === 'repository' || $type === 'wenpai') {
|
if ($type === 'repository' || $type === 'wenpai') {
|
||||||
$item = sanitize_text_field($item);
|
$item = sanitize_text_field($item);
|
||||||
if (!preg_match('/^[a-z0-9-]+$/', $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;
|
$theme_key = $item;
|
||||||
} elseif ($type === 'upload') {
|
} elseif ($type === 'upload') {
|
||||||
$file_name = sanitize_file_name($item['name']);
|
$file = $item;
|
||||||
$theme_key = pathinfo($file_name, PATHINFO_FILENAME);
|
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)) {
|
if (!move_uploaded_file($temp_file, $dest_path)) {
|
||||||
$results[$item] = [
|
throw new Exception('Failed to move uploaded file. Check server permissions.', 2004);
|
||||||
'success' => true,
|
}
|
||||||
'message' => __('Theme already installed, skipped', 'bulk-plugin-installer')
|
|
||||||
];
|
$zip = new ZipArchive();
|
||||||
continue;
|
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) {
|
switch ($type) {
|
||||||
|
@ -217,78 +285,62 @@ class BPI_Installer {
|
||||||
'fields' => ['sections' => false]
|
'fields' => ['sections' => false]
|
||||||
]);
|
]);
|
||||||
if (is_wp_error($api)) {
|
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;
|
break;
|
||||||
|
|
||||||
case 'wenpai':
|
case 'wenpai':
|
||||||
$response = wp_remote_get("https://api.wenpai.net/wp-json/wp/v2/themes/{$item}");
|
$response = wp_remote_get("https://api.wenpai.net/wp-json/wp/v2/themes/{$item}");
|
||||||
if (is_wp_error($response)) {
|
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);
|
$theme_data = json_decode(wp_remote_retrieve_body($response), true);
|
||||||
$download_link = $theme_data && !empty($theme_data['download_link'])
|
$download_link = $theme_data && !empty($theme_data['download_link'])
|
||||||
? $theme_data['download_link']
|
? $theme_data['download_link']
|
||||||
: "https://downloads.wenpai.net/theme/{$item}.latest-stable.zip";
|
: "https://downloads.wenpai.net/theme/{$item}.latest-stable.zip";
|
||||||
$result = $upgrader->install($download_link);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'url':
|
case 'url':
|
||||||
$item = sanitize_text_field($item);
|
$item = sanitize_text_field($item);
|
||||||
if (!filter_var($item, FILTER_VALIDATE_URL) || !bpi_is_domain_allowed($item)) {
|
if (!filter_var($item, FILTER_VALIDATE_URL)) {
|
||||||
throw new Exception(__('Invalid or untrusted URL', 'bulk-plugin-installer'));
|
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;
|
break;
|
||||||
|
|
||||||
case 'upload':
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$result = $upgrader->install($download_link);
|
||||||
|
if ($type === 'upload' && file_exists($dest_path)) {
|
||||||
|
unlink($dest_path);
|
||||||
|
}
|
||||||
|
|
||||||
if (is_wp_error($result)) {
|
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] = [
|
$results[$item] = [
|
||||||
'success' => $result === true,
|
'success' => true,
|
||||||
'message' => $result === true ? __('Successfully installed', 'bulk-plugin-installer') : __('Installation failed', 'bulk-plugin-installer')
|
'message' => 'Successfully installed'
|
||||||
];
|
];
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
$no_retry_codes = [2001, 2003, 2005, 2006, 2009, 2010]; // 无需重试的错误代码
|
||||||
$results[$item] = [
|
$results[$item] = [
|
||||||
'success' => false,
|
'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