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
e682403182
commit
814e65f90e
5 changed files with 1304 additions and 0 deletions
225
admin-page.php
Normal file
225
admin-page.php
Normal file
|
@ -0,0 +1,225 @@
|
|||
<?php
|
||||
function bpi_render_admin_page() {
|
||||
if (!bpi_user_can_install()) {
|
||||
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_localize_script('bpi-admin', 'bpiAjax', [
|
||||
'nonce' => wp_create_nonce('bpi_installer'),
|
||||
'ajaxurl' => admin_url('admin-ajax.php')
|
||||
]);
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php echo esc_html__('Bulk Plugin & Theme Installer', 'bulk-plugin-installer'); ?>
|
||||
<span style="font-size: 13px; padding-left: 10px;"><?php printf(esc_html__('Version: %s', 'bulk-plugin-installer'), esc_html(BPI_VERSION)); ?></span>
|
||||
<a href="https://wpmultisite.com/document/" target="_blank" class="button button-secondary" style="margin-left: 10px;"><?php esc_html_e('Document', 'bulk-plugin-installer'); ?></a>
|
||||
<a href="https://wpmultisite.com/forums/" target="_blank" class="button button-secondary"><?php esc_html_e('Support', 'bulk-plugin-installer'); ?></a>
|
||||
</h1>
|
||||
|
||||
<div class="bpi-container">
|
||||
<div class="bpi-card">
|
||||
<div id="bpi-tabs">
|
||||
<div class="bpi-tabs-nav">
|
||||
<button type="button" class="bpi-tab active" data-tab="plugins"><?php _e('Plugins', 'bulk-plugin-installer'); ?></button>
|
||||
<button type="button" class="bpi-tab" data-tab="themes"><?php _e('Themes', 'bulk-plugin-installer'); ?></button>
|
||||
<button type="button" class="bpi-tab" data-tab="settings"><?php _e('Settings', 'bulk-plugin-installer'); ?></button>
|
||||
</div>
|
||||
|
||||
<div id="plugins" class="bpi-tab-content active">
|
||||
<h2><?php _e('Install Plugins', 'bulk-plugin-installer'); ?></h2>
|
||||
<p><?php _e('Install multiple plugins from various sources.', 'bulk-plugin-installer'); ?></p>
|
||||
<form id="bulk-plugin-form" class="bpi-form" enctype="multipart/form-data">
|
||||
<div class="bpi-form-row">
|
||||
<label for="plugin-install-type"><?php _e('Installation Source:', 'bulk-plugin-installer'); ?></label>
|
||||
<select id="plugin-install-type" name="install_type" class="bpi-select">
|
||||
<option value="repository"><?php _e('WordPress.org Repository', 'bulk-plugin-installer'); ?></option>
|
||||
<option value="wenpai"><?php _e('WenPai.org Repository (China Mirror)', 'bulk-plugin-installer'); ?></option>
|
||||
<option value="url"><?php _e('Remote URL', 'bulk-plugin-installer'); ?></option>
|
||||
<option value="upload"><?php _e('Upload ZIP', 'bulk-plugin-installer'); ?></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="bpi-form-row source-input repository-source active">
|
||||
<label for="plugin-slugs"><?php _e('Plugin Slugs:', 'bulk-plugin-installer'); ?></label>
|
||||
<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'); ?>"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="bpi-form-row source-input wenpai-source">
|
||||
<label for="plugin-wenpai-slugs"><?php _e('Plugin Slugs (WenPai.org):', 'bulk-plugin-installer'); ?></label>
|
||||
<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'); ?>"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="bpi-form-row source-input url-source">
|
||||
<label for="plugin-urls"><?php _e('Download URLs:', 'bulk-plugin-installer'); ?></label>
|
||||
<textarea id="plugin-urls" name="items" rows="8"
|
||||
placeholder="<?php esc_attr_e('Enter download URLs, one per line', 'bulk-plugin-installer'); ?>"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="bpi-form-row source-input upload-source">
|
||||
<label for="plugin-files"><?php _e('ZIP Files:', 'bulk-plugin-installer'); ?></label>
|
||||
<div class="file-upload-container">
|
||||
<input type="file" id="plugin-files" name="plugin_files[]" multiple accept=".zip" />
|
||||
<div class="upload-instructions">
|
||||
<?php _e('Drag and drop plugin ZIP files here or click to select files', 'bulk-plugin-installer'); ?>
|
||||
</div>
|
||||
<div id="selected-files" class="selected-files"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bpi-form-row">
|
||||
<?php wp_nonce_field('bpi_installer', 'bpi_nonce'); ?>
|
||||
<button type="submit" class="button button-primary button-large">
|
||||
<?php _e('Install Plugins', 'bulk-plugin-installer'); ?>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="themes" class="bpi-tab-content">
|
||||
<h2><?php _e('Install Themes', 'bulk-plugin-installer'); ?></h2>
|
||||
<p><?php _e('Install multiple themes from various sources.', 'bulk-plugin-installer'); ?></p>
|
||||
<form id="bulk-theme-form" class="bpi-form" enctype="multipart/form-data">
|
||||
<div class="bpi-form-row">
|
||||
<label for="theme-install-type"><?php _e('Installation Source:', 'bulk-plugin-installer'); ?></label>
|
||||
<select id="theme-install-type" name="install_type" class="bpi-select">
|
||||
<option value="repository"><?php _e('WordPress.org Repository', 'bulk-plugin-installer'); ?></option>
|
||||
<option value="wenpai"><?php _e('WenPai.org Repository (China Mirror)', 'bulk-plugin-installer'); ?></option>
|
||||
<option value="url"><?php _e('Remote URL', 'bulk-plugin-installer'); ?></option>
|
||||
<option value="upload"><?php _e('Upload ZIP', 'bulk-plugin-installer'); ?></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="bpi-form-row source-input repository-source active">
|
||||
<label for="theme-slugs"><?php _e('Theme Slugs:', 'bulk-plugin-installer'); ?></label>
|
||||
<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'); ?>"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="bpi-form-row source-input wenpai-source">
|
||||
<label for="theme-wenpai-slugs"><?php _e('Theme Slugs (WenPai.org):', 'bulk-plugin-installer'); ?></label>
|
||||
<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'); ?>"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="bpi-form-row source-input url-source">
|
||||
<label for="theme-urls"><?php _e('Download URLs:', 'bulk-plugin-installer'); ?></label>
|
||||
<textarea id="theme-urls" name="items" rows="8"
|
||||
placeholder="<?php esc_attr_e('Enter download URLs, one per line', 'bulk-plugin-installer'); ?>"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="bpi-form-row source-input upload-source">
|
||||
<label for="theme-files"><?php _e('ZIP Files:', 'bulk-plugin-installer'); ?></label>
|
||||
<div class="file-upload-container">
|
||||
<input type="file" id="theme-files" name="theme_files[]" multiple accept=".zip" />
|
||||
<div class="upload-instructions">
|
||||
<?php _e('Drag and drop theme ZIP files here or click to select files', 'bulk-plugin-installer'); ?>
|
||||
</div>
|
||||
<div id="selected-files" class="selected-files"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bpi-form-row">
|
||||
<?php wp_nonce_field('bpi_installer', 'bpi_nonce'); ?>
|
||||
<button type="submit" class="button button-primary button-large">
|
||||
<?php _e('Install Themes', 'bulk-plugin-installer'); ?>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="settings" class="bpi-tab-content">
|
||||
<h2><?php _e('Settings', 'bulk-plugin-installer'); ?></h2>
|
||||
<p><?php _e('Configure plugin installation settings.', 'bulk-plugin-installer'); ?></p>
|
||||
<span id="settings-status" class="notice" style="display:none;"></span>
|
||||
<?php if (!current_user_can('manage_options')): ?>
|
||||
<p><?php _e('You need administrator privileges to modify these settings.', 'bulk-plugin-installer'); ?></p>
|
||||
<?php else: ?>
|
||||
<form id="bpi-settings-form" class="bpi-form">
|
||||
<?php wp_nonce_field('bpi_installer', 'bpi_nonce'); ?>
|
||||
<div class="bpi-form-row">
|
||||
<label><?php _e('Allowed Roles', 'bulk-plugin-installer'); ?></label>
|
||||
<?php
|
||||
$allowed_roles = get_option('bpi_allowed_roles', BPI_ALLOWED_ROLES);
|
||||
$roles = wp_roles()->get_names();
|
||||
foreach ($roles as $role => $label) {
|
||||
printf(
|
||||
'<label><input type="checkbox" name="bpi_allowed_roles[]" value="%s" %s> %s</label><br>',
|
||||
esc_attr($role),
|
||||
in_array($role, $allowed_roles) ? 'checked' : '',
|
||||
esc_html($label)
|
||||
);
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<div class="bpi-form-row">
|
||||
<label for="bpi_custom_domains"><?php _e('Additional Trusted Domains', 'bulk-plugin-installer'); ?></label>
|
||||
<textarea name="bpi_custom_domains" id="bpi_custom_domains" rows="5" class="large-text code"
|
||||
placeholder="<?php esc_attr_e('Enter one root domain per line (e.g., example.com)', 'bulk-plugin-installer'); ?>"
|
||||
><?php echo esc_textarea(get_option('bpi_custom_domains', '')); ?></textarea>
|
||||
<p class="description">
|
||||
<?php _e('Enter root domains (one per line) for remote installation. Subdomains are automatically included.', 'bulk-plugin-installer'); ?>
|
||||
</p>
|
||||
</div>
|
||||
<div class="bpi-form-row">
|
||||
<button type="submit" class="button button-primary"><?php _e('Save Settings', 'bulk-plugin-installer'); ?></button>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="installation-results" class="bpi-results"></div>
|
||||
</div>
|
||||
|
||||
<div class="bpi-card">
|
||||
<h2><?php _e('Statistics', 'bulk-plugin-installer'); ?></h2>
|
||||
<p><?php _e('View statistics of plugin and theme installations.', 'bulk-plugin-installer'); ?></p>
|
||||
<?php
|
||||
$stats = get_option('bpi_statistics', [
|
||||
'total_installs' => 0,
|
||||
'successful_installs' => 0,
|
||||
'failed_installs' => 0,
|
||||
'last_install_time' => ''
|
||||
]);
|
||||
?>
|
||||
<table class="wp-list-table widefat fixed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php _e('Metric', 'bulk-plugin-installer'); ?></th>
|
||||
<th><?php _e('Value', 'bulk-plugin-installer'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th><?php _e('Total Installs', 'bulk-plugin-installer'); ?></th>
|
||||
<td><?php echo esc_html($stats['total_installs']); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><?php _e('Successful Installs', 'bulk-plugin-installer'); ?></th>
|
||||
<td><?php echo esc_html($stats['successful_installs']); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><?php _e('Failed Installs', 'bulk-plugin-installer'); ?></th>
|
||||
<td><?php echo esc_html($stats['failed_installs']); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><?php _e('Last Install Time', 'bulk-plugin-installer'); ?></th>
|
||||
<td><?php echo esc_html($stats['last_install_time'] ?: __('Never Installed', 'bulk-plugin-installer')); ?></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
325
bulk-plugin-installer.php
Normal file
325
bulk-plugin-installer.php
Normal file
|
@ -0,0 +1,325 @@
|
|||
<?php
|
||||
/**
|
||||
* 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
|
||||
* Author URI: https://wpmultisite.com
|
||||
* Network: true
|
||||
* Requires at least: 5.8
|
||||
* License: GPL v2 or later
|
||||
* Text Domain: bulk-plugin-installer
|
||||
* Requires PHP: 7.4
|
||||
* Domain Path: /languages
|
||||
*/
|
||||
|
||||
if (!defined('WPINC')) {
|
||||
die;
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
function bpi_init() {
|
||||
if (is_multisite()) {
|
||||
if (is_network_admin()) {
|
||||
add_action('network_admin_menu', 'bpi_add_network_submenu_page');
|
||||
}
|
||||
} else {
|
||||
add_action('admin_menu', 'bpi_add_menu_page');
|
||||
}
|
||||
|
||||
add_action('wp_ajax_bpi_install_plugins', 'bpi_handle_install_plugins');
|
||||
add_action('wp_ajax_bpi_install_themes', 'bpi_handle_install_themes');
|
||||
add_action('wp_ajax_bpi_save_settings', 'bpi_handle_save_settings');
|
||||
}
|
||||
add_action('plugins_loaded', 'bpi_init');
|
||||
|
||||
function bpi_add_menu_page() {
|
||||
add_plugins_page(
|
||||
__('Plugin Installer', 'bulk-plugin-installer'),
|
||||
__('Plugin Installer', 'bulk-plugin-installer'),
|
||||
'install_plugins',
|
||||
'bulk-plugin-installer',
|
||||
'bpi_render_admin_page',
|
||||
10
|
||||
);
|
||||
}
|
||||
|
||||
function bpi_add_network_submenu_page() {
|
||||
add_submenu_page(
|
||||
'plugins.php',
|
||||
__('Plugin Installer', 'bulk-plugin-installer'),
|
||||
__('Plugin Installer', 'bulk-plugin-installer'),
|
||||
'manage_network_plugins',
|
||||
'bulk-plugin-installer',
|
||||
'bpi_render_admin_page'
|
||||
);
|
||||
}
|
||||
|
||||
define('BPI_ALLOWED_ROLES', ['administrator', 'super_admin']);
|
||||
define('BPI_TRUSTED_DOMAINS', [
|
||||
'wordpress.org',
|
||||
'downloads.wordpress.org',
|
||||
'github.com',
|
||||
'raw.githubusercontent.com',
|
||||
'wenpai.cn',
|
||||
'wenpai.net',
|
||||
'wenpai.org',
|
||||
'downloads.wenpai.net',
|
||||
'weixiaoduo.com',
|
||||
'feibisi.com',
|
||||
'feicode.com'
|
||||
]);
|
||||
|
||||
function bpi_register_settings() {
|
||||
register_setting('bpi_settings', 'bpi_allowed_roles', ['sanitize_callback' => 'bpi_sanitize_roles']);
|
||||
register_setting('bpi_settings', 'bpi_custom_domains', ['sanitize_callback' => 'sanitize_textarea_field']);
|
||||
register_setting('bpi_settings', 'bpi_statistics', ['sanitize_callback' => 'bpi_sanitize_statistics']);
|
||||
}
|
||||
add_action('admin_init', 'bpi_register_settings');
|
||||
if (is_multisite()) {
|
||||
add_action('network_admin_init', 'bpi_register_settings');
|
||||
}
|
||||
|
||||
function bpi_sanitize_roles($roles) {
|
||||
if (!is_array($roles)) {
|
||||
return BPI_ALLOWED_ROLES;
|
||||
}
|
||||
$valid_roles = array_keys(wp_roles()->get_names());
|
||||
return array_intersect($roles, $valid_roles);
|
||||
}
|
||||
|
||||
function bpi_sanitize_statistics($stats) {
|
||||
return [
|
||||
'total_installs' => absint($stats['total_installs'] ?? 0),
|
||||
'successful_installs' => absint($stats['successful_installs'] ?? 0),
|
||||
'failed_installs' => absint($stats['failed_installs'] ?? 0),
|
||||
'last_install_time' => sanitize_text_field($stats['last_install_time'] ?? '')
|
||||
];
|
||||
}
|
||||
|
||||
function bpi_user_can_install() {
|
||||
if (!is_user_logged_in()) {
|
||||
return false;
|
||||
}
|
||||
if (is_multisite() && is_network_admin()) {
|
||||
return current_user_can('manage_network_plugins');
|
||||
}
|
||||
$allowed_roles = get_option('bpi_allowed_roles', BPI_ALLOWED_ROLES);
|
||||
$user = wp_get_current_user();
|
||||
return !empty(array_intersect($allowed_roles, $user->roles)) || current_user_can('manage_options');
|
||||
}
|
||||
|
||||
function bpi_is_domain_allowed($url) {
|
||||
if (empty($url)) {
|
||||
return false;
|
||||
}
|
||||
$host = parse_url($url, PHP_URL_HOST);
|
||||
if (!$host) {
|
||||
return false;
|
||||
}
|
||||
$host = strtolower($host);
|
||||
$trusted_domains = array_merge(
|
||||
BPI_TRUSTED_DOMAINS,
|
||||
array_filter(array_map('trim', explode("\n", get_option('bpi_custom_domains', ''))))
|
||||
);
|
||||
$trusted_domains = array_map('strtolower', $trusted_domains);
|
||||
|
||||
foreach ($trusted_domains as $domain) {
|
||||
if ($host === $domain || preg_match('/\.' . preg_quote($domain, '/') . '$/', $host)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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'));
|
||||
}
|
||||
|
||||
$installer = new BPI_Installer();
|
||||
$type = sanitize_text_field($_POST['install_type'] ?? '');
|
||||
$results = [];
|
||||
|
||||
try {
|
||||
if ($type === 'upload') {
|
||||
if (!isset($_FILES['plugin_files']) || empty($_FILES['plugin_files']['name'])) {
|
||||
wp_send_json_error(__('No files uploaded', 'bulk-plugin-installer'));
|
||||
}
|
||||
$files = [];
|
||||
if (is_array($_FILES['plugin_files']['name'])) {
|
||||
$file_count = count($_FILES['plugin_files']['name']);
|
||||
for ($i = 0; $i < $file_count; $i++) {
|
||||
if ($_FILES['plugin_files']['error'][$i] === UPLOAD_ERR_OK) {
|
||||
$files[] = [
|
||||
'name' => sanitize_file_name($_FILES['plugin_files']['name'][$i]),
|
||||
'type' => $_FILES['plugin_files']['type'][$i],
|
||||
'tmp_name' => $_FILES['plugin_files']['tmp_name'][$i],
|
||||
'error' => $_FILES['plugin_files']['error'][$i],
|
||||
'size' => $_FILES['plugin_files']['size'][$i]
|
||||
];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($_FILES['plugin_files']['error'] === UPLOAD_ERR_OK) {
|
||||
$files[] = [
|
||||
'name' => sanitize_file_name($_FILES['plugin_files']['name']),
|
||||
'type' => $_FILES['plugin_files']['type'],
|
||||
'tmp_name' => $_FILES['plugin_files']['tmp_name'],
|
||||
'error' => $_FILES['plugin_files']['error'],
|
||||
'size' => $_FILES['plugin_files']['size']
|
||||
];
|
||||
}
|
||||
}
|
||||
if (empty($files)) {
|
||||
wp_send_json_error(__('No valid files uploaded', 'bulk-plugin-installer'));
|
||||
}
|
||||
$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'));
|
||||
}
|
||||
$results = $installer->bpi_install_plugins($items, $type);
|
||||
}
|
||||
bpi_update_statistics($results);
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
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'));
|
||||
}
|
||||
|
||||
$installer = new BPI_Installer();
|
||||
$type = sanitize_text_field($_POST['install_type'] ?? '');
|
||||
$results = [];
|
||||
|
||||
try {
|
||||
if ($type === 'upload') {
|
||||
if (!isset($_FILES['theme_files']) || empty($_FILES['theme_files']['name'])) {
|
||||
wp_send_json_error(__('No files uploaded', 'bulk-plugin-installer'));
|
||||
}
|
||||
$files = [];
|
||||
if (is_array($_FILES['theme_files']['name'])) {
|
||||
$file_count = count($_FILES['theme_files']['name']);
|
||||
for ($i = 0; $i < $file_count; $i++) {
|
||||
if ($_FILES['theme_files']['error'][$i] === UPLOAD_ERR_OK) {
|
||||
$files[] = [
|
||||
'name' => sanitize_file_name($_FILES['theme_files']['name'][$i]),
|
||||
'type' => $_FILES['theme_files']['type'][$i],
|
||||
'tmp_name' => $_FILES['theme_files']['tmp_name'][$i],
|
||||
'error' => $_FILES['theme_files']['error'][$i],
|
||||
'size' => $_FILES['theme_files']['size'][$i]
|
||||
];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($_FILES['theme_files']['error'] === UPLOAD_ERR_OK) {
|
||||
$files[] = [
|
||||
'name' => sanitize_file_name($_FILES['theme_files']['name']),
|
||||
'type' => $_FILES['theme_files']['type'],
|
||||
'tmp_name' => $_FILES['theme_files']['tmp_name'],
|
||||
'error' => $_FILES['theme_files']['error'],
|
||||
'size' => $_FILES['theme_files']['size']
|
||||
];
|
||||
}
|
||||
}
|
||||
if (empty($files)) {
|
||||
wp_send_json_error(__('No valid files uploaded', 'bulk-plugin-installer'));
|
||||
}
|
||||
$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'));
|
||||
}
|
||||
$results = $installer->bpi_install_themes($items, $type);
|
||||
}
|
||||
bpi_update_statistics($results);
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
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'));
|
||||
}
|
||||
|
||||
$roles = isset($_POST['bpi_allowed_roles']) ? (array)$_POST['bpi_allowed_roles'] : [];
|
||||
$domains = isset($_POST['bpi_custom_domains']) ? sanitize_textarea_field($_POST['bpi_custom_domains']) : '';
|
||||
|
||||
update_option('bpi_allowed_roles', bpi_sanitize_roles($roles));
|
||||
update_option('bpi_custom_domains', $domains);
|
||||
|
||||
wp_send_json_success(__('Settings saved successfully!', 'bulk-plugin-installer'));
|
||||
}
|
||||
|
||||
function bpi_update_statistics($results) {
|
||||
$stats = get_option('bpi_statistics', [
|
||||
'total_installs' => 0,
|
||||
'successful_installs' => 0,
|
||||
'failed_installs' => 0,
|
||||
'last_install_time' => ''
|
||||
]);
|
||||
|
||||
$stats['total_installs'] += count($results);
|
||||
foreach ($results as $result) {
|
||||
if ($result['success']) {
|
||||
$stats['successful_installs']++;
|
||||
} else {
|
||||
$stats['failed_installs']++;
|
||||
}
|
||||
}
|
||||
$stats['last_install_time'] = current_time('mysql');
|
||||
|
||||
update_option('bpi_statistics', $stats);
|
||||
}
|
||||
|
||||
register_activation_hook(__FILE__, 'bpi_activate');
|
||||
function bpi_activate() {
|
||||
if (version_compare(PHP_VERSION, '7.4', '<')) {
|
||||
deactivate_plugins(plugin_basename(__FILE__));
|
||||
wp_die(__('This plugin requires PHP 7.4 or higher.', 'bulk-plugin-installer'));
|
||||
}
|
||||
$suggested_configs = [
|
||||
'upload_max_filesize' => '64M',
|
||||
'post_max_size' => '64M',
|
||||
'max_file_uploads' => '20',
|
||||
'memory_limit' => '256M',
|
||||
'max_execution_time' => '300'
|
||||
];
|
||||
foreach ($suggested_configs as $key => $value) {
|
||||
if (ini_get($key) < $value) {
|
||||
error_log("BPI Warning: $key is set to " . ini_get($key) . ", recommended: $value");
|
||||
}
|
||||
}
|
||||
if (!get_option('bpi_statistics')) {
|
||||
update_option('bpi_statistics', [
|
||||
'total_installs' => 0,
|
||||
'successful_installs' => 0,
|
||||
'failed_installs' => 0,
|
||||
'last_install_time' => ''
|
||||
]);
|
||||
}
|
||||
}
|
298
class-installer.php
Normal file
298
class-installer.php
Normal file
|
@ -0,0 +1,298 @@
|
|||
<?php
|
||||
class BPI_Installer {
|
||||
private $wp_filesystem;
|
||||
|
||||
public function __construct() {
|
||||
global $wp_filesystem;
|
||||
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'));
|
||||
}
|
||||
}
|
||||
$this->wp_filesystem = $wp_filesystem;
|
||||
}
|
||||
|
||||
private function is_plugin_zip($zip_path) {
|
||||
$zip = new ZipArchive();
|
||||
if ($zip->open($zip_path) === true) {
|
||||
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||
$filename = $zip->getNameIndex($i);
|
||||
if (preg_match('/\.php$/', $filename)) {
|
||||
$content = $zip->getFromIndex($i);
|
||||
if (preg_match('/Plugin Name:/i', $content)) {
|
||||
$zip->close();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
$zip->close();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function is_theme_zip($zip_path) {
|
||||
$zip = new ZipArchive();
|
||||
if ($zip->open($zip_path) === true) {
|
||||
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||
if (strpos($zip->getNameIndex($i), 'style.css') !== false) {
|
||||
$zip->close();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
$zip->close();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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')];
|
||||
}
|
||||
|
||||
if (empty($items) || !is_array($items)) {
|
||||
return ['error' => __('No items provided', 'bulk-plugin-installer')];
|
||||
}
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
|
||||
$results = [];
|
||||
$upgrader = new Plugin_Upgrader(new WP_Ajax_Upgrader_Skin());
|
||||
$installed_plugins = get_plugins();
|
||||
|
||||
foreach ($items as $item) {
|
||||
try {
|
||||
$plugin_key = null;
|
||||
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'));
|
||||
}
|
||||
$plugin_key = $item . '/' . $item . '.php';
|
||||
} elseif ($type === 'upload') {
|
||||
$file_name = sanitize_file_name($item['name']);
|
||||
$plugin_key = pathinfo($file_name, PATHINFO_FILENAME) . '/' . pathinfo($file_name, PATHINFO_FILENAME) . '.php';
|
||||
}
|
||||
|
||||
if ($plugin_key && array_key_exists($plugin_key, $installed_plugins)) {
|
||||
$results[$item] = [
|
||||
'success' => true,
|
||||
'message' => __('Plugin already installed, skipped', 'bulk-plugin-installer')
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'repository':
|
||||
$api = plugins_api('plugin_information', [
|
||||
'slug' => $item,
|
||||
'fields' => ['sections' => false]
|
||||
]);
|
||||
if (is_wp_error($api)) {
|
||||
throw new Exception($api->get_error_message());
|
||||
}
|
||||
$result = $upgrader->install($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());
|
||||
}
|
||||
$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'));
|
||||
}
|
||||
$result = $upgrader->install($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_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'));
|
||||
}
|
||||
unlink($dest_path);
|
||||
throw new Exception(__('Invalid plugin ZIP file', 'bulk-plugin-installer'));
|
||||
}
|
||||
|
||||
$result = $upgrader->install($dest_path);
|
||||
if (file_exists($dest_path)) {
|
||||
unlink($dest_path);
|
||||
}
|
||||
$item = $file_name;
|
||||
break;
|
||||
}
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
throw new Exception($result->get_error_message());
|
||||
}
|
||||
|
||||
$results[$item] = [
|
||||
'success' => $result === true,
|
||||
'message' => $result === true ? __('Successfully installed', 'bulk-plugin-installer') : __('Installation failed', 'bulk-plugin-installer')
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
$results[$item] = [
|
||||
'success' => false,
|
||||
'message' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
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')];
|
||||
}
|
||||
|
||||
if (empty($items) || !is_array($items)) {
|
||||
return ['error' => __('No items provided', 'bulk-plugin-installer')];
|
||||
}
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/theme-install.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
|
||||
|
||||
$results = [];
|
||||
$upgrader = new Theme_Upgrader(new WP_Ajax_Upgrader_Skin());
|
||||
$installed_themes = wp_get_themes();
|
||||
|
||||
foreach ($items as $item) {
|
||||
try {
|
||||
$theme_key = null;
|
||||
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'));
|
||||
}
|
||||
$theme_key = $item;
|
||||
} elseif ($type === 'upload') {
|
||||
$file_name = sanitize_file_name($item['name']);
|
||||
$theme_key = pathinfo($file_name, PATHINFO_FILENAME);
|
||||
}
|
||||
|
||||
if ($theme_key && array_key_exists($theme_key, $installed_themes)) {
|
||||
$results[$item] = [
|
||||
'success' => true,
|
||||
'message' => __('Theme already installed, skipped', 'bulk-plugin-installer')
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'repository':
|
||||
$api = themes_api('theme_information', [
|
||||
'slug' => $item,
|
||||
'fields' => ['sections' => false]
|
||||
]);
|
||||
if (is_wp_error($api)) {
|
||||
throw new Exception($api->get_error_message());
|
||||
}
|
||||
$result = $upgrader->install($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());
|
||||
}
|
||||
$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'));
|
||||
}
|
||||
$result = $upgrader->install($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;
|
||||
}
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
throw new Exception($result->get_error_message());
|
||||
}
|
||||
|
||||
$results[$item] = [
|
||||
'success' => $result === true,
|
||||
'message' => $result === true ? __('Successfully installed', 'bulk-plugin-installer') : __('Installation failed', 'bulk-plugin-installer')
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
$results[$item] = [
|
||||
'success' => false,
|
||||
'message' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
195
css/admin.css
Normal file
195
css/admin.css
Normal file
|
@ -0,0 +1,195 @@
|
|||
.bpi-wrap {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.bpi-container {
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
.bpi-card {
|
||||
background: #fff;
|
||||
border: 1px solid #ccd0d4;
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.bpi-tabs-nav {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
border-bottom: 1px solid #c3c4c7;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.bpi-tab {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
border-bottom: 2px solid transparent;
|
||||
color: #23282d;
|
||||
}
|
||||
|
||||
.bpi-tab.active {
|
||||
border-bottom: 2px solid #007cba;
|
||||
font-weight: 600;
|
||||
background: #f0f0f1;
|
||||
}
|
||||
|
||||
.bpi-tab:hover:not(.active) {
|
||||
background: #f0f0f1;
|
||||
border-bottom-color: #dcdcde;
|
||||
}
|
||||
|
||||
.bpi-tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bpi-tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.bpi-form-row {
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.bpi-form-row label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.bpi-select {
|
||||
min-width: 200px;
|
||||
height: 35px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.bpi-form textarea {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #c3c4c7;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.file-upload-container {
|
||||
border: 2px dashed #b4b9be;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
background: #f9f9f9;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.file-upload-container:hover,
|
||||
.file-upload-container.dragover {
|
||||
border-color: #007cba;
|
||||
background: #f0f0f1;
|
||||
}
|
||||
|
||||
.file-upload-container input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.upload-instructions {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.selected-files {
|
||||
margin-top: 15px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.selected-file {
|
||||
background: #fff;
|
||||
padding: 8px 12px;
|
||||
margin: 5px 0;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.selected-file .remove-file {
|
||||
color: #dc3232;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.source-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.source-input.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.bpi-results .notice,
|
||||
#settings-status {
|
||||
padding: 8px 12px;
|
||||
border-radius: 3px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.bpi-results .notice-success,
|
||||
#settings-status.notice-success {
|
||||
background-color: #dff0d8;
|
||||
border-left: 4px solid #46b450;
|
||||
}
|
||||
|
||||
.bpi-results .notice-error,
|
||||
#settings-status.notice-error {
|
||||
background-color: #f2dede;
|
||||
border-left: 4px solid #dc3232;
|
||||
}
|
||||
|
||||
.bpi-results .notice-info,
|
||||
#settings-status.notice-info {
|
||||
background-color: #e5f5fa;
|
||||
border-left: 4px solid #00a0d2;
|
||||
}
|
||||
|
||||
.installation-list {
|
||||
list-style: none;
|
||||
margin: 10px 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.installation-list li {
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.installation-list .spinner {
|
||||
margin: 0 10px 0 0;
|
||||
}
|
||||
|
||||
.installation-list .item-name {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.installation-list .status {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.installation-list .success {
|
||||
color: #46b450;
|
||||
}
|
||||
|
||||
.installation-list .error {
|
||||
color: #dc3232;
|
||||
}
|
||||
|
||||
.progress-count {
|
||||
padding: 10px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
margin: 10px 0;
|
||||
}
|
261
js/admin.js
Normal file
261
js/admin.js
Normal file
|
@ -0,0 +1,261 @@
|
|||
jQuery(document).ready(function($) {
|
||||
$('.bpi-tab').on('click', function() {
|
||||
$('.bpi-tab').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
|
||||
var tab = $(this).data('tab');
|
||||
$('.bpi-tab-content').removeClass('active').hide();
|
||||
$('#' + tab).addClass('active').show();
|
||||
});
|
||||
|
||||
$('.bpi-select').on('change', function() {
|
||||
const $form = $(this).closest('.bpi-form');
|
||||
const selectedType = $(this).val();
|
||||
|
||||
$form.find('.source-input').removeClass('active').hide();
|
||||
$form.find('textarea[name="items"]').val('');
|
||||
$form.find('input[type="file"]').val('');
|
||||
$form.find('.selected-files').empty();
|
||||
|
||||
$form.find('.' + selectedType + '-source').addClass('active').show();
|
||||
});
|
||||
|
||||
$('.file-upload-container').each(function() {
|
||||
const $container = $(this);
|
||||
const $fileInput = $container.find('input[type="file"]');
|
||||
const $selectedFiles = $container.find('.selected-files');
|
||||
|
||||
$container.on('click', function(e) {
|
||||
if (e.target === this || $(e.target).hasClass('upload-instructions')) {
|
||||
$fileInput.trigger('click');
|
||||
}
|
||||
});
|
||||
|
||||
$container.on('dragover', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$(this).addClass('dragover');
|
||||
});
|
||||
|
||||
$container.on('dragleave drop', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$(this).removeClass('dragover');
|
||||
});
|
||||
|
||||
$container.on('drop', function(e) {
|
||||
const files = e.originalEvent.dataTransfer.files;
|
||||
$fileInput[0].files = files;
|
||||
handleFiles(files);
|
||||
});
|
||||
|
||||
$fileInput.on('change', function() {
|
||||
handleFiles(this.files);
|
||||
});
|
||||
|
||||
function handleFiles(files) {
|
||||
$selectedFiles.empty();
|
||||
Array.from(files).forEach(file => {
|
||||
if (file.type === 'application/zip' || file.name.endsWith('.zip')) {
|
||||
const $fileElement = $(`
|
||||
<div class="selected-file">
|
||||
<span class="filename">${escapeHtml(file.name)}</span>
|
||||
<span class="remove-file dashicons dashicons-no-alt"></span>
|
||||
</div>
|
||||
`);
|
||||
$selectedFiles.append($fileElement);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$selectedFiles.on('click', '.remove-file', function() {
|
||||
$(this).closest('.selected-file').remove();
|
||||
if ($selectedFiles.children().length === 0) {
|
||||
$fileInput.val('');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#bulk-plugin-form, #bulk-theme-form').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const $form = $(this);
|
||||
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 = '';
|
||||
|
||||
if (type === 'upload') {
|
||||
const $fileInput = $form.find('input[type="file"]');
|
||||
const files = $fileInput[0].files;
|
||||
if (!files || files.length === 0) {
|
||||
errorMessage = 'Please select at least one ZIP file.';
|
||||
} else {
|
||||
items = Array.from(files).map(file => file.name);
|
||||
}
|
||||
} else {
|
||||
const $textarea = $form.find('.' + type + '-source textarea[name="items"]');
|
||||
items = $textarea.val().split('\n')
|
||||
.map(item => item.trim())
|
||||
.filter(item => item.length > 0);
|
||||
if (items.length === 0) {
|
||||
errorMessage = (type === 'repository' || type === 'wenpai') ?
|
||||
'Please enter at least one slug.' :
|
||||
'Please enter at least one URL.';
|
||||
}
|
||||
}
|
||||
|
||||
if (errorMessage) {
|
||||
alert(errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
$submitButton.prop('disabled', true).text('Installing...');
|
||||
$results.html(`<div class="notice notice-info"><p>Installation in progress... (Large ZIP files may take some time)</p><div class="progress-count">0/${items.length} completed (0% done, ${items.length} remaining)</div><ul class="installation-list"></ul></div>`);
|
||||
|
||||
const $list = $results.find('.installation-list');
|
||||
const $progress = $results.find('.progress-count');
|
||||
let completed = 0;
|
||||
|
||||
if (type === 'upload') {
|
||||
const formData = new FormData($form[0]);
|
||||
formData.append('action', action);
|
||||
formData.append('nonce', bpiAjax.nonce);
|
||||
formData.append('install_type', type);
|
||||
|
||||
$.ajax({
|
||||
url: bpiAjax.ajaxurl,
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: function(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>`);
|
||||
}
|
||||
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>`);
|
||||
installationComplete();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
processNextItem(0);
|
||||
}
|
||||
|
||||
function processNextItem(index) {
|
||||
if (index >= items.length) {
|
||||
installationComplete();
|
||||
return;
|
||||
}
|
||||
|
||||
const item = items[index];
|
||||
$list.append(`<li id="item-${index}"><span class="spinner is-active"></span><span class="item-name">${escapeHtml(item)}</span><span class="status"></span></li>`);
|
||||
|
||||
$.ajax({
|
||||
url: bpiAjax.ajaxurl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: action,
|
||||
nonce: bpiAjax.nonce,
|
||||
items: JSON.stringify([item]),
|
||||
install_type: type
|
||||
},
|
||||
success: function(response) {
|
||||
handleResponse(response, item, index);
|
||||
processNextItem(index + 1);
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
handleError(xhr, status, error, item, index);
|
||||
processNextItem(index + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleResponse(response, item, index) {
|
||||
const $item = $(`#item-${index}`) || $list.find('li:last');
|
||||
$item.find('.spinner').removeClass('is-active');
|
||||
|
||||
if (response.success) {
|
||||
const result = response.data[item];
|
||||
$item.addClass(result.success ? 'success' : 'error')
|
||||
.find('.status').text(result.success ? '✓ ' + result.message : '✗ ' + result.message);
|
||||
} else {
|
||||
$item.addClass('error').find('.status').text('✗ ' + (response.data || 'Unknown error'));
|
||||
}
|
||||
|
||||
completed++;
|
||||
const percentage = Math.round((completed / items.length) * 100);
|
||||
const remaining = items.length - completed;
|
||||
$progress.text(`${completed}/${items.length} completed (${percentage}% done, ${remaining} remaining)`);
|
||||
}
|
||||
|
||||
function handleError(xhr, status, error, item, index) {
|
||||
const $item = $(`#item-${index}`) || $list.find('li:last');
|
||||
$item.find('.spinner').removeClass('is-active')
|
||||
.addClass('error')
|
||||
.find('.status').text(`✗ Installation failed: ${xhr.responseText || error}`);
|
||||
completed++;
|
||||
const percentage = Math.round((completed / items.length) * 100);
|
||||
const remaining = items.length - completed;
|
||||
$progress.text(`${completed}/${items.length} completed (${percentage}% done, ${remaining} remaining)`);
|
||||
}
|
||||
|
||||
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!');
|
||||
}
|
||||
});
|
||||
|
||||
$('#bpi-settings-form').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const $form = $(this);
|
||||
const $submitButton = $form.find('button[type="submit"]');
|
||||
const $status = $('#settings-status');
|
||||
|
||||
$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',
|
||||
data: {
|
||||
action: 'bpi_save_settings',
|
||||
nonce: bpiAjax.nonce,
|
||||
bpi_allowed_roles: $form.find('input[name="bpi_allowed_roles[]"]:checked').map(function() { return this.value; }).get(),
|
||||
bpi_custom_domains: $form.find('textarea[name="bpi_custom_domains"]').val()
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
$status.removeClass('notice-info').addClass('notice-success').text(response.data || 'Settings saved successfully!').show().delay(3000).fadeOut();
|
||||
} else {
|
||||
$status.removeClass('notice-info').addClass('notice-error').text(response.data || 'Failed to save settings.').show();
|
||||
}
|
||||
$submitButton.prop('disabled', false).text('Save Settings');
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
$status.removeClass('notice-info').addClass('notice-error').text('An error occurred while saving settings: ' + (xhr.responseText || error)).show();
|
||||
$submitButton.prop('disabled', false).text('Save Settings');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function escapeHtml(unsafe) {
|
||||
return unsafe
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue