dev 添加远程合集功能

This commit is contained in:
文派备案 2025-05-20 00:39:06 +08:00
parent 7f12edb6ae
commit 93d19b6da0
9 changed files with 3884 additions and 170 deletions

View file

@ -1,3 +1,8 @@
/**
* Admin styles for Bulk Plugin Installer
*/
/* General Layout */
.bpi-wrap {
margin: 20px 0;
}
@ -12,8 +17,10 @@
border-radius: 4px;
padding: 20px;
margin-top: 20px;
box-shadow: 0 1px 1px rgba(0,0,0,0.04);
}
/* Tabs Navigation */
.bpi-tabs-nav {
display: flex;
flex-wrap: wrap;
@ -30,10 +37,11 @@
font-size: 14px;
border-bottom: 2px solid transparent;
color: #23282d;
transition: all 0.2s ease;
}
.bpi-tab.active {
border-bottom: 2px solid #007cba;
border-bottom: 2px solid #2271b1;
font-weight: 600;
background: #f0f0f1;
}
@ -45,12 +53,19 @@
.bpi-tab-content {
display: none;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
0% { opacity: 0; }
100% { opacity: 1; }
}
.bpi-tab-content.active {
display: block;
}
/* Form Elements */
.bpi-form-row {
margin: 15px 0;
}
@ -74,6 +89,7 @@
border-radius: 4px;
}
/* File Upload */
.file-upload-container {
border: 2px dashed #b4b9be;
padding: 20px;
@ -86,7 +102,7 @@
.file-upload-container:hover,
.file-upload-container.dragover {
border-color: #007cba;
border-color: #2271b1;
background: #f0f0f1;
}
@ -121,6 +137,7 @@
cursor: pointer;
}
/* Source Inputs */
.source-input {
display: none;
}
@ -129,6 +146,7 @@
display: block;
}
/* Notifications */
.bpi-results .notice,
#settings-status {
padding: 8px 12px;
@ -138,13 +156,13 @@
.bpi-results .notice-success,
#settings-status.notice-success {
background-color: #dff0d8;
background-color: #edfaef;
border-left: 4px solid #46b450;
}
.bpi-results .notice-error,
#settings-status.notice-error {
background-color: #f2dede;
background-color: #fcf0f1;
border-left: 4px solid #dc3232;
}
@ -154,6 +172,7 @@
border-left: 4px solid #00a0d2;
}
/* Installation List */
.installation-list {
list-style: none;
margin: 10px 0 0;
@ -165,6 +184,14 @@
border-bottom: 1px solid #f0f0f0;
display: flex;
align-items: center;
border-radius: 4px;
background: #f8f9fa;
margin-bottom: 5px;
transition: background 0.2s;
}
.installation-list li:hover {
background: #f0f0f1;
}
.installation-list .spinner {
@ -187,6 +214,7 @@
color: #dc3232;
}
/* Progress Indicator */
.progress-count {
padding: 10px;
background: #f8f9fa;
@ -194,8 +222,29 @@
margin: 10px 0;
}
.progress-bar-container {
height: 20px;
background-color: #eee;
border-radius: 10px;
margin: 10px 0;
overflow: hidden;
}
.progress-bar {
height: 100%;
background-color: #2271b1;
border-radius: 10px;
text-align: center;
line-height: 20px;
color: #fff;
font-size: 12px;
font-weight: bold;
transition: width 0.5s ease;
}
/* Action Buttons */
.retry-btn {
background: #007cba;
background: #2271b1;
color: #fff;
border: none;
padding: 2px 8px;
@ -206,10 +255,520 @@
}
.retry-btn:hover {
background: #005a87;
background: #135e96;
}
.upload.button {
margin-left: 10px;
vertical-align: top;
}
/* Collections Styles */
.bpi-collections-wrapper {
position: relative;
min-height: 200px;
}
.bpi-collections-wrapper.loading:before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.7);
z-index: 1;
}
.bpi-collections-wrapper.loading:after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 30px;
height: 30px;
margin: -15px 0 0 -15px;
border: 4px solid #f3f3f3;
border-top: 4px solid #2271b1;
border-radius: 50%;
z-index: 2;
animation: bpi-spin 1s linear infinite;
}
@keyframes bpi-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.bpi-collections-controls {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.bpi-collections-filters {
display: flex;
gap: 10px;
}
.bpi-collections-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
margin-top: 20px;
}
.bpi-collection-card {
background: #fff;
border: 1px solid #ddd;
border-radius: 6px;
padding: 20px;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.bpi-collection-card:hover {
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.bpi-collection-icon {
margin-bottom: 15px;
}
.bpi-collection-icon .dashicons {
font-size: 36px;
width: 36px;
height: 36px;
color: #2271b1;
}
.bpi-collection-card h3 {
margin: 0 0 10px 0;
font-size: 18px;
color: #23282d;
}
.bpi-collection-card p {
color: #646970;
margin: 0 0 15px 0;
font-size: 13px;
}
.bpi-collection-meta {
display: flex;
gap: 15px;
margin-bottom: 15px;
font-size: 12px;
color: #50575e;
}
.bpi-back-to-collections {
background: none;
border: none;
padding: 10px 0;
cursor: pointer;
color: #2271b1;
display: inline-flex;
align-items: center;
margin-bottom: 20px;
}
.bpi-back-to-collections:hover {
color: #135e96;
}
.bpi-back-to-collections .dashicons {
margin-right: 5px;
}
.bpi-collection-details h2 {
margin-top: 0;
}
.bpi-collection-plugins, .bpi-collection-themes {
margin-top: 20px;
}
.bpi-collection-plugins h3, .bpi-collection-themes h3 {
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.bpi-collection-item-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 15px;
}
.bpi-collection-item {
background: #f8f9fa;
padding: 10px;
border-radius: 4px;
display: flex;
align-items: center;
}
.bpi-collection-item-icon {
width: 24px;
height: 24px;
margin-right: 10px;
display: flex;
align-items: center;
justify-content: center;
background: #e9f0f5;
border-radius: 4px;
color: #2271b1;
}
.bpi-collection-item-name {
font-size: 13px;
flex-grow: 1;
}
.bpi-collection-item-description {
font-size: 11px;
color: #666;
margin-top: 4px;
}
.bpi-required {
font-size: 11px;
color: #d63638;
margin-left: 5px;
}
.bpi-collection-item-source {
font-size: 10px;
color: #777;
background: #eee;
padding: 2px 5px;
border-radius: 3px;
}
.bpi-install-collection-wrapper {
background: #f0f6fc;
padding: 15px;
border-left: 4px solid #2271b1;
margin: 20px 0;
display: flex;
align-items: center;
justify-content: space-between;
}
.bpi-install-collection-wrapper p {
margin: 0;
flex-grow: 1;
}
.bpi-collection-screenshot {
margin: 15px 0;
text-align: center;
}
.bpi-collection-screenshot img {
max-width: 100%;
border-radius: 4px;
border: 1px solid #ddd;
}
.bpi-empty-collections {
grid-column: 1 / -1;
padding: 30px;
text-align: center;
background: #f8f9fa;
border-radius: 4px;
color: #646970;
}
/* Settings - Collection Sources */
.bpi-sources-table {
margin-top: 15px;
}
.bpi-toggle {
position: relative;
display: inline-block;
width: 40px;
height: 20px;
}
.bpi-toggle input {
opacity: 0;
width: 0;
height: 0;
}
.bpi-toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 34px;
}
.bpi-toggle-slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 2px;
bottom: 2px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .bpi-toggle-slider {
background-color: #2271b1;
}
input:checked + .bpi-toggle-slider:before {
transform: translateX(20px);
}
/* Logs styles */
.bpi-log-filters {
margin-bottom: 20px;
background: #f8f9fa;
padding: 15px;
border-radius: 4px;
}
.bpi-filter-row {
display: flex;
gap: 10px;
flex-wrap: wrap;
align-items: center;
}
.bpi-status {
padding: 3px 8px;
border-radius: 3px;
font-size: 12px;
font-weight: bold;
}
.bpi-status-success {
background-color: #edfaef;
color: #46b450;
}
.bpi-status-error {
background-color: #fcf0f1;
color: #dc3232;
}
.bpi-status-skipped {
background-color: #f8f9fa;
color: #555;
}
.bpi-log-actions {
margin-top: 20px;
display: flex;
gap: 10px;
}
.bpi-radio-group {
margin-bottom: 15px;
}
.bpi-radio-group label {
display: inline-block;
margin-right: 15px;
font-weight: normal;
}
/* Reinstall tab styles */
.bpi-reinstall-tabs {
display: flex;
margin-bottom: 20px;
border-bottom: 1px solid #ddd;
}
.bpi-reinstall-tab {
padding: 10px 15px;
background: none;
border: none;
border-bottom: 2px solid transparent;
margin-right: 10px;
cursor: pointer;
font-weight: 500;
}
.bpi-reinstall-tab.active {
border-bottom-color: #2271b1;
color: #2271b1;
}
.bpi-reinstall-content {
display: none;
}
.bpi-reinstall-content.active {
display: block;
}
.bpi-reinstall-actions {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-bottom: 15px;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
}
.bpi-search-box {
flex: 1;
}
.bpi-search-box input {
width: 100%;
padding: 6px 8px;
}
.bpi-filter-actions,
.bpi-selection-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.bpi-plugins-table,
.bpi-themes-table {
margin-bottom: 20px;
}
.bpi-plugins-table .check-column,
.bpi-themes-table .check-column {
width: 2%;
}
.plugin-description,
.theme-description {
color: #646970;
font-size: 12px;
margin-top: 3px;
}
.bpi-source {
display: inline-block;
padding: 2px 6px;
border-radius: 3px;
font-size: 12px;
}
.source-wp {
background-color: #e5f7ff;
color: #0073aa;
}
.source-wenpai {
background-color: #edf7ed;
color: #298029;
}
.source-unknown {
background-color: #f8f0e3;
color: #c25000;
}
.status-active {
color: #008a20;
}
.status-inactive {
color: #646970;
}
.bpi-manual-note {
font-size: 11px;
color: #646970;
font-style: italic;
}
.bpi-bulk-actions {
margin-top: 20px;
display: flex;
align-items: flex-start;
gap: 15px;
}
.bpi-warning {
background: #fff8e5;
border-left: 4px solid #ffb900;
padding: 8px 12px;
margin: 0;
font-size: 13px;
align-self: center;
}
.bpi-warning .dashicons {
color: #ffb900;
margin-right: 5px;
}
/* Responsive Styles */
@media (max-width: 782px) {
.bpi-collections-grid {
grid-template-columns: 1fr;
}
.bpi-collection-item-list {
grid-template-columns: 1fr;
}
.bpi-install-collection-wrapper {
flex-direction: column;
align-items: flex-start;
}
.bpi-install-collection-wrapper .button {
margin-top: 10px;
width: 100%;
text-align: center;
}
.bpi-collections-controls {
flex-direction: column;
gap: 10px;
align-items: flex-start;
}
.bpi-collections-filters {
width: 100%;
}
.bpi-collections-filters select {
flex: 1;
}
.bpi-filter-row {
flex-direction: column;
align-items: flex-start;
}
.bpi-filter-row > * {
width: 100%;
margin-bottom: 5px;
}
.bpi-log-actions {
flex-direction: column;
}
.bpi-log-actions .button {
width: 100%;
text-align: center;
margin-bottom: 5px;
}
}

File diff suppressed because it is too large Load diff

View file

@ -2,8 +2,8 @@
/**
* Plugin Name: Bulk Plugin Installer
* Plugin URI: https://wpmultisite.com/plugins/bulk-plugin-installer/
* Description: Bulk install WordPress plugins and themes from repository, URL, or ZIP uploads.
* Version: 1.1.8
* Description: Bulk install WordPress plugins and themes from repository, URL, or ZIP uploads with preset collections support.
* Version: 1.3.0
* Author: WPMultisite.com
* Author URI: https://wpmultisite.com
* Network: true
@ -18,52 +18,9 @@ if (!defined('WPINC')) {
die;
}
define('BPI_VERSION', '1.1.8');
define('BPI_VERSION', '1.2.0');
define('BPI_PATH', plugin_dir_path(__FILE__));
define('BPI_URL', plugin_dir_url(__FILE__));
require_once BPI_PATH . 'includes/class-installer.php';
require_once BPI_PATH . 'includes/admin-page.php';
function bpi_init() {
load_plugin_textdomain('bulk-plugin-installer', false, dirname(plugin_basename(__FILE__)) . '/languages/');
if (is_multisite()) {
if (is_network_admin()) {
add_action('network_admin_menu', 'bpi_add_network_submenu_page');
}
} 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',
@ -77,16 +34,87 @@ define('BPI_TRUSTED_DOMAINS', [
'raw.githubusercontent.com'
]);
// Include required files
require_once BPI_PATH . 'includes/class-installer.php';
require_once BPI_PATH . 'includes/admin-page.php';
require_once BPI_PATH . 'includes/collections.php';
require_once BPI_PATH . 'includes/logging.php';
require_once BPI_PATH . 'includes/reinstaller.php';
/**
* Initialize the plugin
*/
function bpi_init() {
load_plugin_textdomain('bulk-plugin-installer', false, dirname(plugin_basename(__FILE__)) . '/languages/');
if (is_multisite()) {
if (is_network_admin()) {
add_action('network_admin_menu', 'bpi_add_network_submenu_page');
}
} else {
add_action('admin_menu', 'bpi_add_menu_page');
}
// Register Ajax handlers
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('wp_ajax_bpi_get_collection_details', 'bpi_ajax_get_collection_details');
add_action('wp_ajax_bpi_install_collection', 'bpi_ajax_install_collection');
add_action('wp_ajax_bpi_save_remote_source', 'bpi_ajax_save_remote_source');
add_action('wp_ajax_bpi_refresh_collections', 'bpi_ajax_refresh_collections');
}
add_action('plugins_loaded', 'bpi_init');
/**
* Add menu page for single site
*/
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
);
}
/**
* Add menu page for multisite
*/
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'
);
}
/**
* Register plugin settings
*/
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']);
register_setting('bpi_settings', 'bpi_collection_sources', ['sanitize_callback' => 'bpi_sanitize_collection_sources']);
register_setting('bpi_settings', 'bpi_install_options', ['sanitize_callback' => 'bpi_sanitize_install_options']);
}
add_action('admin_init', 'bpi_register_settings');
if (is_multisite()) {
add_action('network_admin_init', 'bpi_register_settings');
}
/**
* Sanitize the roles array
*
* @param array $roles User roles
* @return array Sanitized roles
*/
function bpi_sanitize_roles($roles) {
if (!is_array($roles)) {
return BPI_ALLOWED_ROLES;
@ -95,6 +123,12 @@ function bpi_sanitize_roles($roles) {
return array_intersect($roles, $valid_roles);
}
/**
* Sanitize statistics array
*
* @param array $stats Statistics data
* @return array Sanitized statistics
*/
function bpi_sanitize_statistics($stats) {
return [
'total_installs' => absint($stats['total_installs'] ?? 0),
@ -104,6 +138,60 @@ function bpi_sanitize_statistics($stats) {
];
}
/**
* Sanitize collection sources
*
* @param array $sources Collection source URLs
* @return array Sanitized source URLs
*/
function bpi_sanitize_collection_sources($sources) {
if (!is_array($sources)) {
return [];
}
$sanitized = [];
foreach ($sources as $source) {
if (isset($source['url']) && !empty($source['url'])) {
$sanitized[] = [
'name' => sanitize_text_field($source['name'] ?? __('Unnamed Source', 'bulk-plugin-installer')),
'url' => esc_url_raw($source['url']),
'enabled' => !empty($source['enabled'])
];
}
}
return $sanitized;
}
/**
* Sanitize install options
*
* @param array $options Installation options
* @return array Sanitized options
*/
function bpi_sanitize_install_options($options) {
if (!is_array($options)) {
return [
'duplicate_handling' => 'skip',
'auto_activate' => false,
'keep_backups' => false,
];
}
return [
'duplicate_handling' => isset($options['duplicate_handling']) && in_array($options['duplicate_handling'], ['skip', 'reinstall', 'error'])
? $options['duplicate_handling']
: 'skip',
'auto_activate' => !empty($options['auto_activate']),
'keep_backups' => !empty($options['keep_backups']),
];
}
/**
* Check if current user can install plugins
*
* @return bool True if user can install, false otherwise
*/
function bpi_user_can_install() {
if (!is_user_logged_in()) {
return false;
@ -116,6 +204,12 @@ function bpi_user_can_install() {
return !empty(array_intersect($allowed_roles, $user->roles)) || current_user_can('manage_options');
}
/**
* Check if a domain is in the allowed list
*
* @param string $url URL to check
* @return bool True if domain is allowed, false otherwise
*/
function bpi_is_domain_allowed($url) {
if (empty($url)) {
return false;
@ -139,6 +233,9 @@ function bpi_is_domain_allowed($url) {
return false;
}
/**
* Handle plugin installation AJAX request
*/
function bpi_handle_install_plugins() {
check_ajax_referer('bpi_installer', 'nonce');
@ -161,31 +258,7 @@ function bpi_handle_install_plugins() {
'error_code' => 400
]);
}
$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']
];
}
}
$files = bpi_process_uploaded_files('plugin_files');
if (empty($files)) {
wp_send_json_error([
'message' => __('No valid files uploaded', 'bulk-plugin-installer'),
@ -214,6 +287,44 @@ function bpi_handle_install_plugins() {
}
}
/**
* Process uploaded files
*
* @param string $field_name Form field name
* @return array Processed files
*/
function bpi_process_uploaded_files($field_name) {
$files = [];
if (is_array($_FILES[$field_name]['name'])) {
$file_count = count($_FILES[$field_name]['name']);
for ($i = 0; $i < $file_count; $i++) {
if ($_FILES[$field_name]['error'][$i] === UPLOAD_ERR_OK) {
$files[] = [
'name' => sanitize_file_name($_FILES[$field_name]['name'][$i]),
'type' => $_FILES[$field_name]['type'][$i],
'tmp_name' => $_FILES[$field_name]['tmp_name'][$i],
'error' => $_FILES[$field_name]['error'][$i],
'size' => $_FILES[$field_name]['size'][$i]
];
}
}
} else {
if ($_FILES[$field_name]['error'] === UPLOAD_ERR_OK) {
$files[] = [
'name' => sanitize_file_name($_FILES[$field_name]['name']),
'type' => $_FILES[$field_name]['type'],
'tmp_name' => $_FILES[$field_name]['tmp_name'],
'error' => $_FILES[$field_name]['error'],
'size' => $_FILES[$field_name]['size']
];
}
}
return $files;
}
/**
* Handle theme installation AJAX request
*/
function bpi_handle_install_themes() {
check_ajax_referer('bpi_installer', 'nonce');
@ -236,31 +347,7 @@ function bpi_handle_install_themes() {
'error_code' => 400
]);
}
$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']
];
}
}
$files = bpi_process_uploaded_files('theme_files');
if (empty($files)) {
wp_send_json_error([
'message' => __('No valid files uploaded', 'bulk-plugin-installer'),
@ -289,6 +376,9 @@ function bpi_handle_install_themes() {
}
}
/**
* Handle save settings AJAX request
*/
function bpi_handle_save_settings() {
check_ajax_referer('bpi_installer', 'nonce');
@ -302,12 +392,26 @@ function bpi_handle_save_settings() {
$roles = isset($_POST['bpi_allowed_roles']) ? (array)$_POST['bpi_allowed_roles'] : [];
$domains = isset($_POST['bpi_custom_domains']) ? sanitize_textarea_field($_POST['bpi_custom_domains']) : '';
// Save installation options
$install_options = isset($_POST['bpi_install_options']) ? $_POST['bpi_install_options'] : [];
$sanitized_options = [
'duplicate_handling' => isset($install_options['duplicate_handling']) ? sanitize_text_field($install_options['duplicate_handling']) : 'skip',
'auto_activate' => !empty($install_options['auto_activate']),
'keep_backups' => !empty($install_options['keep_backups']),
];
update_option('bpi_install_options', $sanitized_options);
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'));
}
/**
* Update installation statistics
*
* @param array $results Installation results
*/
function bpi_update_statistics($results) {
$stats = get_option('bpi_statistics', [
'total_installs' => 0,
@ -329,12 +433,17 @@ function bpi_update_statistics($results) {
update_option('bpi_statistics', $stats);
}
/**
* Plugin activation hook
*/
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'));
}
// Check server configuration
$suggested_configs = [
'upload_max_filesize' => '64M',
'post_max_size' => '64M',
@ -347,6 +456,8 @@ function bpi_activate() {
error_log("BPI Warning: $key is set to " . ini_get($key) . ", recommended: $value");
}
}
// Initialize statistics
if (!get_option('bpi_statistics')) {
update_option('bpi_statistics', [
'total_installs' => 0,
@ -355,9 +466,89 @@ function bpi_activate() {
'last_install_time' => ''
]);
}
// Create default collection sources
if (!get_option('bpi_collection_sources')) {
update_option('bpi_collection_sources', [
[
'name' => __('Official Collections', 'bulk-plugin-installer'),
'url' => 'https://wpmultisite.com/api/collections.json',
'enabled' => true
]
]);
}
// Create required directories
$collections_dir = BPI_PATH . 'data';
if (!file_exists($collections_dir)) {
wp_mkdir_p($collections_dir);
}
// Create backup directory
$backup_dir = WP_CONTENT_DIR . '/bpi-backups';
if (!file_exists($backup_dir)) {
wp_mkdir_p($backup_dir);
wp_mkdir_p($backup_dir . '/plugins');
wp_mkdir_p($backup_dir . '/themes');
}
// Create logs table
bpi_create_logs_table();
// Create default collections file if it doesn't exist
$collections_file = $collections_dir . '/collections.json';
if (!file_exists($collections_file)) {
$default_collections = [
'version' => '1.0',
'last_updated' => current_time('Y-m-d'),
'collections' => [
'business' => [
'name' => __('Business Website', 'bulk-plugin-installer'),
'slug' => 'business',
'description' => __('Essential plugins for a professional business website.', 'bulk-plugin-installer'),
'icon' => 'dashicons-building',
'category' => 'business',
'level' => 'beginner',
'plugins' => [
'repository' => [
[
'slug' => 'wordpress-seo',
'name' => 'Yoast SEO',
'description' => __('The leading SEO plugin for WordPress', 'bulk-plugin-installer'),
'required' => true
],
[
'slug' => 'contact-form-7',
'name' => 'Contact Form 7',
'description' => __('Simple but flexible contact form plugin', 'bulk-plugin-installer'),
'required' => true
]
],
'wenpai' => [],
'url' => []
],
'themes' => [
'repository' => [
[
'slug' => 'astra',
'name' => 'Astra',
'description' => __('Fast, lightweight theme for business websites', 'bulk-plugin-installer'),
'required' => false
]
],
'wenpai' => [],
'url' => []
]
]
]
];
file_put_contents($collections_file, wp_json_encode($default_collections, JSON_PRETTY_PRINT));
}
}
// Add "Upload from URL" button to plugin install page
/**
* Add "Upload from URL" button to plugin install page
*/
function bpi_add_plugin_url_upload_button() {
if (bpi_user_can_install()) {
?>
@ -373,7 +564,9 @@ function bpi_add_plugin_url_upload_button() {
}
add_action('admin_footer-plugin-install.php', 'bpi_add_plugin_url_upload_button');
// Add "Upload from URL" button to theme install page
/**
* Add "Upload from URL" button to theme install page
*/
function bpi_add_theme_url_upload_button() {
if (bpi_user_can_install()) {
?>
@ -389,6 +582,41 @@ function bpi_add_theme_url_upload_button() {
}
add_action('admin_footer-theme-install.php', 'bpi_add_theme_url_upload_button');
/**
* Add "Upload from URL" button to installed plugins list page
*/
function bpi_add_installed_plugin_url_upload_button() {
if (bpi_user_can_install()) {
?>
<script type="text/javascript">
jQuery(document).ready(function($) {
$('.wp-header-end').before(
'<a href="<?php echo admin_url("plugins.php?page=bulk-plugin-installer&tab=plugins&install_type=url"); ?>" class="page-title-action"><?php _e("Upload from URL", "bulk-plugin-installer"); ?></a>'
);
});
</script>
<?php
}
}
add_action('admin_footer-plugins.php', 'bpi_add_installed_plugin_url_upload_button');
/**
* Add "Upload from URL" button to installed themes list page
*/
function bpi_add_installed_theme_url_upload_button() {
if (bpi_user_can_install()) {
?>
<script type="text/javascript">
jQuery(document).ready(function($) {
$('.wp-header-end').before(
'<a href="<?php echo admin_url("plugins.php?page=bulk-plugin-installer&tab=themes&install_type=url"); ?>" class="page-title-action"><?php _e("Upload from URL", "bulk-plugin-installer"); ?></a>'
);
});
</script>
<?php
}
}
add_action('admin_footer-themes.php', 'bpi_add_installed_theme_url_upload_button');
// Integrate UpdatePulse Server for updates using PUC v5.3
require_once plugin_dir_path(__FILE__) . 'lib/plugin-update-checker/plugin-update-checker.php';

44
data/collections.json Normal file
View file

@ -0,0 +1,44 @@
{
"version": "1.0",
"last_updated": "2025-05-17",
"collections": {
"business": {
"name": "Business Website",
"slug": "business",
"description": "Essential plugins for a professional business website.",
"icon": "dashicons-building",
"category": "business",
"level": "beginner",
"plugins": {
"repository": [
{
"slug": "wordpress-seo",
"name": "Yoast SEO",
"description": "The leading SEO plugin for WordPress",
"required": true
},
{
"slug": "contact-form-7",
"name": "Contact Form 7",
"description": "Simple but flexible contact form plugin",
"required": true
}
],
"wenpai": [],
"url": []
},
"themes": {
"repository": [
{
"slug": "astra",
"name": "Astra",
"description": "Fast, lightweight theme for business websites",
"required": false
}
],
"wenpai": [],
"url": []
}
}
}
}

View file

@ -1,4 +1,11 @@
<?php
/**
* Admin page rendering functions
*/
/**
* Render the admin page
*/
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'));
@ -18,7 +25,13 @@ function bpi_render_admin_page() {
'installation_complete' => __('Installation completed! Check the results below. Failed items can be retried using the "Retry" buttons if applicable.', 'bulk-plugin-installer'),
'no_files' => __('Please select at least one ZIP file.', 'bulk-plugin-installer'),
'no_slugs' => __('Please enter at least one slug.', 'bulk-plugin-installer'),
'no_urls' => __('Please enter at least one URL.', 'bulk-plugin-installer')
'no_urls' => __('Please enter at least one URL.', 'bulk-plugin-installer'),
'confirm_install_collection' => __('Are you sure you want to install all items in this collection?', 'bulk-plugin-installer'),
'installing' => __('Installing...', 'bulk-plugin-installer'),
'install_collection' => __('Install Collection', 'bulk-plugin-installer'),
'confirm_delete_source' => __('Are you sure you want to delete this collection source?', 'bulk-plugin-installer'),
'reinstall' => __('Reinstall', 'bulk-plugin-installer'),
'manual_reinstall' => __('Manual reinstall required', 'bulk-plugin-installer')
];
wp_localize_script('bpi-admin', 'bpiAjax', [
@ -40,6 +53,10 @@ function bpi_render_admin_page() {
<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="reinstall"><?php _e('Reinstall', 'bulk-plugin-installer'); ?></button>
<button type="button" class="bpi-tab" data-tab="collections"><?php _e('Collections', 'bulk-plugin-installer'); ?></button>
<button type="button" class="bpi-tab" data-tab="collection-sources"><?php _e('Collection Sources', 'bulk-plugin-installer'); ?></button>
<button type="button" class="bpi-tab" data-tab="logs"><?php _e('Logs', 'bulk-plugin-installer'); ?></button>
<button type="button" class="bpi-tab" data-tab="settings"><?php _e('Settings', 'bulk-plugin-installer'); ?></button>
</div>
@ -153,6 +170,440 @@ function bpi_render_admin_page() {
</form>
</div>
<div id="reinstall" class="bpi-tab-content">
<h2><?php _e('Reinstall Plugins & Themes', 'bulk-plugin-installer'); ?></h2>
<p><?php _e('Use this tool to reinstall plugins and themes after a security incident or to fix corrupted files. Note that custom or premium plugins/themes from unknown sources must be manually reinstalled.', 'bulk-plugin-installer'); ?></p>
<div class="bpi-reinstall-container">
<div class="bpi-reinstall-tabs">
<button type="button" class="bpi-reinstall-tab active" data-content="plugins"><?php _e('Plugins', 'bulk-plugin-installer'); ?></button>
<button type="button" class="bpi-reinstall-tab" data-content="themes"><?php _e('Themes', 'bulk-plugin-installer'); ?></button>
</div>
<div class="bpi-reinstall-content active" id="bpi-reinstall-plugins">
<div class="bpi-reinstall-actions">
<div class="bpi-search-box">
<input type="text" id="plugin-search" placeholder="<?php esc_attr_e('Search plugins...', 'bulk-plugin-installer'); ?>">
</div>
<div class="bpi-filter-actions">
<label>
<input type="checkbox" id="filter-active-plugins" checked>
<?php _e('Active', 'bulk-plugin-installer'); ?>
</label>
<label>
<input type="checkbox" id="filter-inactive-plugins" checked>
<?php _e('Inactive', 'bulk-plugin-installer'); ?>
</label>
<label>
<input type="checkbox" id="filter-wp-plugins" checked>
<?php _e('WordPress.org', 'bulk-plugin-installer'); ?>
</label>
<label>
<input type="checkbox" id="filter-unknown-plugins" checked>
<?php _e('Unknown Source', 'bulk-plugin-installer'); ?>
</label>
</div>
<div class="bpi-selection-actions">
<button type="button" class="button" id="select-all-plugins"><?php _e('Select All', 'bulk-plugin-installer'); ?></button>
<button type="button" class="button" id="select-none-plugins"><?php _e('Select None', 'bulk-plugin-installer'); ?></button>
<button type="button" class="button" id="select-wp-plugins"><?php _e('Select WordPress.org', 'bulk-plugin-installer'); ?></button>
</div>
</div>
<table class="wp-list-table widefat fixed striped bpi-plugins-table">
<thead>
<tr>
<th class="check-column"><input type="checkbox" id="plugins-select-all"></th>
<th><?php _e('Plugin', 'bulk-plugin-installer'); ?></th>
<th><?php _e('Version', 'bulk-plugin-installer'); ?></th>
<th><?php _e('Status', 'bulk-plugin-installer'); ?></th>
<th><?php _e('Source', 'bulk-plugin-installer'); ?></th>
<th><?php _e('Actions', 'bulk-plugin-installer'); ?></th>
</tr>
</thead>
<tbody id="plugins-list">
<tr>
<td colspan="6"><?php _e('Loading plugins...', 'bulk-plugin-installer'); ?></td>
</tr>
</tbody>
</table>
<div class="bpi-bulk-actions">
<button type="button" class="button button-primary" id="reinstall-selected-plugins" disabled>
<?php _e('Reinstall Selected Plugins', 'bulk-plugin-installer'); ?>
</button>
<p class="bpi-warning">
<span class="dashicons dashicons-warning"></span>
<?php _e('Warning: Reinstalling will temporarily deactivate the plugins. Premium plugins or those from unknown sources cannot be automatically reinstalled.', 'bulk-plugin-installer'); ?>
</p>
</div>
</div>
<div class="bpi-reinstall-content" id="bpi-reinstall-themes">
<div class="bpi-reinstall-actions">
<div class="bpi-search-box">
<input type="text" id="theme-search" placeholder="<?php esc_attr_e('Search themes...', 'bulk-plugin-installer'); ?>">
</div>
<div class="bpi-filter-actions">
<label>
<input type="checkbox" id="filter-active-themes" checked>
<?php _e('Active', 'bulk-plugin-installer'); ?>
</label>
<label>
<input type="checkbox" id="filter-inactive-themes" checked>
<?php _e('Inactive', 'bulk-plugin-installer'); ?>
</label>
<label>
<input type="checkbox" id="filter-wp-themes" checked>
<?php _e('WordPress.org', 'bulk-plugin-installer'); ?>
</label>
<label>
<input type="checkbox" id="filter-unknown-themes" checked>
<?php _e('Unknown Source', 'bulk-plugin-installer'); ?>
</label>
</div>
<div class="bpi-selection-actions">
<button type="button" class="button" id="select-all-themes"><?php _e('Select All', 'bulk-plugin-installer'); ?></button>
<button type="button" class="button" id="select-none-themes"><?php _e('Select None', 'bulk-plugin-installer'); ?></button>
<button type="button" class="button" id="select-wp-themes"><?php _e('Select WordPress.org', 'bulk-plugin-installer'); ?></button>
</div>
</div>
<table class="wp-list-table widefat fixed striped bpi-themes-table">
<thead>
<tr>
<th class="check-column"><input type="checkbox" id="themes-select-all"></th>
<th><?php _e('Theme', 'bulk-plugin-installer'); ?></th>
<th><?php _e('Version', 'bulk-plugin-installer'); ?></th>
<th><?php _e('Status', 'bulk-plugin-installer'); ?></th>
<th><?php _e('Source', 'bulk-plugin-installer'); ?></th>
<th><?php _e('Actions', 'bulk-plugin-installer'); ?></th>
</tr>
</thead>
<tbody id="themes-list">
<tr>
<td colspan="6"><?php _e('Loading themes...', 'bulk-plugin-installer'); ?></td>
</tr>
</tbody>
</table>
<div class="bpi-bulk-actions">
<button type="button" class="button button-primary" id="reinstall-selected-themes" disabled>
<?php _e('Reinstall Selected Themes', 'bulk-plugin-installer'); ?>
</button>
<p class="bpi-warning">
<span class="dashicons dashicons-warning"></span>
<?php _e('Warning: Reinstalling the active theme may briefly affect your site appearance. Premium themes or those from unknown sources cannot be automatically reinstalled.', 'bulk-plugin-installer'); ?>
</p>
</div>
</div>
</div>
<div id="reinstall-results" class="bpi-results"></div>
</div>
<div id="collections" class="bpi-tab-content">
<h2><?php _e('Preset Collections', 'bulk-plugin-installer'); ?></h2>
<p><?php _e('Quickly install curated collections of plugins and themes for specific website types.', 'bulk-plugin-installer'); ?></p>
<div class="bpi-collections-wrapper">
<div class="bpi-collections-controls">
<button type="button" class="button bpi-refresh-collections">
<span class="dashicons dashicons-update"></span> <?php _e('Refresh Collections', 'bulk-plugin-installer'); ?>
</button>
<div class="bpi-collections-filters">
<select id="collection-category-filter">
<option value=""><?php _e('All Categories', 'bulk-plugin-installer'); ?></option>
<option value="business"><?php _e('Business', 'bulk-plugin-installer'); ?></option>
<option value="ecommerce"><?php _e('E-Commerce', 'bulk-plugin-installer'); ?></option>
<option value="community"><?php _e('Community', 'bulk-plugin-installer'); ?></option>
<option value="content"><?php _e('Content', 'bulk-plugin-installer'); ?></option>
</select>
<select id="collection-level-filter">
<option value=""><?php _e('All Levels', 'bulk-plugin-installer'); ?></option>
<option value="beginner"><?php _e('Beginner', 'bulk-plugin-installer'); ?></option>
<option value="intermediate"><?php _e('Intermediate', 'bulk-plugin-installer'); ?></option>
<option value="advanced"><?php _e('Advanced', 'bulk-plugin-installer'); ?></option>
</select>
</div>
</div>
<div class="bpi-collections-grid">
<?php
$collections = bpi_get_preset_collections();
if (empty($collections['collections'])) {
echo '<div class="bpi-empty-collections">';
echo '<p>' . __('No collections found. Please add collection sources in the settings tab or refresh.', 'bulk-plugin-installer') . '</p>';
echo '</div>';
} else {
foreach ($collections['collections'] as $key => $collection) :
$icon = isset($collection['icon']) ? $collection['icon'] : 'dashicons-admin-plugins';
$category = isset($collection['category']) ? $collection['category'] : '';
$level = isset($collection['level']) ? $collection['level'] : '';
?>
<div class="bpi-collection-card" data-collection="<?php echo esc_attr($key); ?>" data-category="<?php echo esc_attr($category); ?>" data-level="<?php echo esc_attr($level); ?>">
<div class="bpi-collection-icon">
<span class="dashicons <?php echo esc_attr($icon); ?>"></span>
</div>
<h3><?php echo esc_html($collection['name']); ?></h3>
<p><?php echo esc_html($collection['description']); ?></p>
<div class="bpi-collection-meta">
<span><?php printf(_n('%s plugin', '%s plugins', bpi_count_collection_items($collection['plugins'] ?? []), 'bulk-plugin-installer'), bpi_count_collection_items($collection['plugins'] ?? [])); ?></span>
<?php if (!empty($collection['themes'])) : ?>
<span><?php printf(_n('%s theme', '%s themes', bpi_count_collection_items($collection['themes'] ?? []), 'bulk-plugin-installer'), bpi_count_collection_items($collection['themes'] ?? [])); ?></span>
<?php endif; ?>
</div>
<button type="button" class="button button-primary bpi-load-collection" data-collection="<?php echo esc_attr($key); ?>">
<?php _e('View & Install', 'bulk-plugin-installer'); ?>
</button>
</div>
<?php endforeach;
}
?>
</div>
<div class="bpi-collection-details" style="display: none;">
<button type="button" class="bpi-back-to-collections">
<span class="dashicons dashicons-arrow-left-alt"></span>
<?php _e('Back to Collections', 'bulk-plugin-installer'); ?>
</button>
<div id="bpi-collection-content"></div>
</div>
</div>
</div>
<div id="collection-sources" class="bpi-tab-content">
<h2><?php _e('Collection Sources', 'bulk-plugin-installer'); ?></h2>
<p><?php _e('Add or manage sources for plugin and theme collections.', 'bulk-plugin-installer'); ?></p>
<?php if (!current_user_can('manage_options')): ?>
<p><?php _e('You need administrator privileges to modify collection sources.', 'bulk-plugin-installer'); ?></p>
<?php else: ?>
<table class="wp-list-table widefat fixed striped bpi-sources-table">
<thead>
<tr>
<th><?php _e('Name', 'bulk-plugin-installer'); ?></th>
<th><?php _e('URL', 'bulk-plugin-installer'); ?></th>
<th><?php _e('Status', 'bulk-plugin-installer'); ?></th>
<th><?php _e('Actions', 'bulk-plugin-installer'); ?></th>
</tr>
</thead>
<tbody>
<?php
$sources = get_option('bpi_collection_sources', []);
if (empty($sources)) {
echo '<tr><td colspan="4">' . __('No sources added yet. Add a source below.', 'bulk-plugin-installer') . '</td></tr>';
} else {
foreach ($sources as $index => $source) {
?>
<tr>
<td><?php echo esc_html($source['name']); ?></td>
<td><a href="<?php echo esc_url($source['url']); ?>" target="_blank"><?php echo esc_url($source['url']); ?></a></td>
<td>
<label class="bpi-toggle">
<input type="checkbox" class="bpi-source-toggle" data-id="<?php echo esc_attr($index); ?>" <?php checked(!empty($source['enabled'])); ?>>
<span class="bpi-toggle-slider"></span>
</label>
</td>
<td>
<button type="button" class="button button-small bpi-edit-source" data-id="<?php echo esc_attr($index); ?>" data-name="<?php echo esc_attr($source['name']); ?>" data-url="<?php echo esc_url($source['url']); ?>">
<?php _e('Edit', 'bulk-plugin-installer'); ?>
</button>
<button type="button" class="button button-small bpi-delete-source" data-id="<?php echo esc_attr($index); ?>">
<?php _e('Delete', 'bulk-plugin-installer'); ?>
</button>
</td>
</tr>
<?php
}
}
?>
</tbody>
</table>
<div class="bpi-form-row" style="margin-top: 20px;">
<h4><?php _e('Add New Source', 'bulk-plugin-installer'); ?></h4>
<form id="bpi-add-source-form" class="bpi-form">
<input type="hidden" id="source-id" value="">
<div class="bpi-form-row">
<label for="source-name"><?php _e('Name', 'bulk-plugin-installer'); ?></label>
<input type="text" id="source-name" name="source_name" class="regular-text" placeholder="<?php esc_attr_e('e.g., My Collection Source', 'bulk-plugin-installer'); ?>">
</div>
<div class="bpi-form-row">
<label for="source-url"><?php _e('URL', 'bulk-plugin-installer'); ?></label>
<input type="url" id="source-url" name="source_url" class="regular-text" placeholder="<?php esc_attr_e('https://example.com/collections.json', 'bulk-plugin-installer'); ?>" required>
<p class="description">
<?php _e('Enter the URL to a JSON file with collections data.', 'bulk-plugin-installer'); ?>
</p>
</div>
<div class="bpi-form-row">
<button type="submit" class="button button-primary" id="source-submit-btn">
<?php _e('Add Source', 'bulk-plugin-installer'); ?>
</button>
<button type="button" class="button" id="source-cancel-btn" style="display:none;">
<?php _e('Cancel', 'bulk-plugin-installer'); ?>
</button>
</div>
</form>
</div>
<?php endif; ?>
</div>
<div id="logs" class="bpi-tab-content">
<h2><?php _e('Installation Logs', 'bulk-plugin-installer'); ?></h2>
<p><?php _e('View logs of plugin and theme installations.', 'bulk-plugin-installer'); ?></p>
<?php if (isset($_GET['cleared']) && $_GET['cleared'] == '1'): ?>
<div class="notice notice-success">
<p><?php _e('Logs have been cleared successfully.', 'bulk-plugin-installer'); ?></p>
</div>
<?php endif; ?>
<div class="bpi-log-filters">
<form id="bpi-log-filter-form" method="get">
<input type="hidden" name="page" value="<?php echo esc_attr($_GET['page']); ?>">
<input type="hidden" name="tab" value="logs">
<div class="bpi-filter-row">
<select name="filter_type">
<option value=""><?php _e('All Types', 'bulk-plugin-installer'); ?></option>
<option value="plugin" <?php selected(isset($_GET['filter_type']) && $_GET['filter_type'] === 'plugin'); ?>><?php _e('Plugins', 'bulk-plugin-installer'); ?></option>
<option value="theme" <?php selected(isset($_GET['filter_type']) && $_GET['filter_type'] === 'theme'); ?>><?php _e('Themes', 'bulk-plugin-installer'); ?></option>
</select>
<select name="filter_status">
<option value=""><?php _e('All Statuses', 'bulk-plugin-installer'); ?></option>
<option value="success" <?php selected(isset($_GET['filter_status']) && $_GET['filter_status'] === 'success'); ?>><?php _e('Success', 'bulk-plugin-installer'); ?></option>
<option value="error" <?php selected(isset($_GET['filter_status']) && $_GET['filter_status'] === 'error'); ?>><?php _e('Error', 'bulk-plugin-installer'); ?></option>
<option value="skipped" <?php selected(isset($_GET['filter_status']) && $_GET['filter_status'] === 'skipped'); ?>><?php _e('Skipped', 'bulk-plugin-installer'); ?></option>
</select>
<input type="date" name="filter_date_from" value="<?php echo isset($_GET['filter_date_from']) ? esc_attr($_GET['filter_date_from']) : ''; ?>" placeholder="<?php esc_attr_e('From Date', 'bulk-plugin-installer'); ?>">
<input type="date" name="filter_date_to" value="<?php echo isset($_GET['filter_date_to']) ? esc_attr($_GET['filter_date_to']) : ''; ?>" placeholder="<?php esc_attr_e('To Date', 'bulk-plugin-installer'); ?>">
<input type="text" name="filter_search" value="<?php echo isset($_GET['filter_search']) ? esc_attr($_GET['filter_search']) : ''; ?>" placeholder="<?php esc_attr_e('Search logs...', 'bulk-plugin-installer'); ?>">
<button type="submit" class="button"><?php _e('Filter', 'bulk-plugin-installer'); ?></button>
<a href="<?php echo esc_url(add_query_arg(['page' => $_GET['page'], 'tab' => 'logs'], admin_url('plugins.php'))); ?>" class="button"><?php _e('Reset', 'bulk-plugin-installer'); ?></a>
</div>
</form>
</div>
<?php
// Process filters
$filters = [];
if (isset($_GET['filter_type']) && !empty($_GET['filter_type'])) {
$filters['item_type'] = sanitize_text_field($_GET['filter_type']);
}
if (isset($_GET['filter_status']) && !empty($_GET['filter_status'])) {
$filters['status'] = sanitize_text_field($_GET['filter_status']);
}
if (isset($_GET['filter_date_from']) && !empty($_GET['filter_date_from'])) {
$filters['date_from'] = sanitize_text_field($_GET['filter_date_from']);
}
if (isset($_GET['filter_date_to']) && !empty($_GET['filter_date_to'])) {
$filters['date_to'] = sanitize_text_field($_GET['filter_date_to']);
}
if (isset($_GET['filter_search']) && !empty($_GET['filter_search'])) {
$filters['search'] = sanitize_text_field($_GET['filter_search']);
}
// Get current page
$current_page = isset($_GET['paged']) ? absint($_GET['paged']) : 1;
// Get logs
$logs_data = bpi_get_logs($filters, 20, $current_page);
$logs = $logs_data['logs'];
$total_pages = $logs_data['pages'];
?>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th><?php _e('Time', 'bulk-plugin-installer'); ?></th>
<th><?php _e('Item', 'bulk-plugin-installer'); ?></th>
<th><?php _e('Type', 'bulk-plugin-installer'); ?></th>
<th><?php _e('Action', 'bulk-plugin-installer'); ?></th>
<th><?php _e('Source', 'bulk-plugin-installer'); ?></th>
<th><?php _e('Status', 'bulk-plugin-installer'); ?></th>
<th><?php _e('Message', 'bulk-plugin-installer'); ?></th>
<th><?php _e('User', 'bulk-plugin-installer'); ?></th>
</tr>
</thead>
<tbody>
<?php if (empty($logs)): ?>
<tr>
<td colspan="8"><?php _e('No logs found.', 'bulk-plugin-installer'); ?></td>
</tr>
<?php else: ?>
<?php foreach ($logs as $log): ?>
<tr>
<td><?php echo esc_html(date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime($log->log_time))); ?></td>
<td><?php echo esc_html($log->item_name); ?></td>
<td><?php echo esc_html(ucfirst($log->item_type)); ?></td>
<td><?php echo esc_html(ucfirst($log->action)); ?></td>
<td><?php echo esc_html(ucfirst($log->source)); ?></td>
<td>
<span class="bpi-status bpi-status-<?php echo esc_attr($log->status); ?>">
<?php echo esc_html(ucfirst($log->status)); ?>
</span>
</td>
<td><?php echo esc_html($log->message); ?></td>
<td>
<?php
$user = get_userdata($log->user_id);
echo $user ? esc_html($user->display_name) : __('Unknown', 'bulk-plugin-installer');
?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<?php if ($total_pages > 1): ?>
<div class="tablenav bottom">
<div class="tablenav-pages">
<span class="displaying-num">
<?php printf(_n('%s item', '%s items', $logs_data['total'], 'bulk-plugin-installer'), number_format_i18n($logs_data['total'])); ?>
</span>
<span class="pagination-links">
<?php
echo paginate_links([
'base' => add_query_arg('paged', '%#%'),
'format' => '',
'prev_text' => '&laquo;',
'next_text' => '&raquo;',
'total' => $total_pages,
'current' => $current_page
]);
?>
</span>
</div>
</div>
<?php endif; ?>
<div class="bpi-log-actions">
<form id="bpi-log-clear-form" method="post">
<?php wp_nonce_field('bpi_clear_logs', 'bpi_logs_nonce'); ?>
<input type="hidden" name="action" value="bpi_clear_logs">
<button type="submit" class="button button-secondary" onclick="return confirm('<?php esc_attr_e('Are you sure you want to clear all logs? This action cannot be undone.', 'bulk-plugin-installer'); ?>');">
<?php _e('Clear All Logs', 'bulk-plugin-installer'); ?>
</button>
</form>
<a href="<?php echo esc_url(wp_nonce_url(add_query_arg(['action' => 'bpi_export_logs'], admin_url('admin-post.php')), 'bpi_export_logs', 'bpi_logs_nonce')); ?>" class="button button-secondary">
<?php _e('Export Logs (CSV)', 'bulk-plugin-installer'); ?>
</a>
</div>
</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>
@ -177,6 +628,45 @@ function bpi_render_admin_page() {
}
?>
</div>
<div class="bpi-form-row">
<h3><?php _e('Installation Options', 'bulk-plugin-installer'); ?></h3>
<?php
$install_options = get_option('bpi_install_options', [
'duplicate_handling' => 'skip',
'auto_activate' => false,
'keep_backups' => false,
]);
?>
<label><?php _e('Duplicate Handling', 'bulk-plugin-installer'); ?></label>
<div class="bpi-radio-group">
<label>
<input type="radio" name="bpi_install_options[duplicate_handling]" value="skip" <?php checked($install_options['duplicate_handling'], 'skip'); ?>>
<?php _e('Skip duplicates (recommended)', 'bulk-plugin-installer'); ?>
</label><br>
<label>
<input type="radio" name="bpi_install_options[duplicate_handling]" value="reinstall" <?php checked($install_options['duplicate_handling'], 'reinstall'); ?>>
<?php _e('Force reinstall', 'bulk-plugin-installer'); ?>
</label><br>
<label>
<input type="radio" name="bpi_install_options[duplicate_handling]" value="error" <?php checked($install_options['duplicate_handling'], 'error'); ?>>
<?php _e('Show error', 'bulk-plugin-installer'); ?>
</label>
</div>
</div>
<div class="bpi-form-row">
<label>
<input type="checkbox" name="bpi_install_options[auto_activate]" value="1" <?php checked(!empty($install_options['auto_activate'])); ?>>
<?php _e('Automatically activate plugins after installation', 'bulk-plugin-installer'); ?>
</label>
</div>
<div class="bpi-form-row">
<label>
<input type="checkbox" name="bpi_install_options[keep_backups]" value="1" <?php checked(!empty($install_options['keep_backups'])); ?>>
<?php _e('Keep backups of existing plugins when reinstalling', 'bulk-plugin-installer'); ?>
</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"

View file

@ -1,8 +1,25 @@
<?php
/**
* Installer class for handling plugin and theme installations
*/
class BPI_Installer {
/**
* WordPress filesystem object
*
* @var WP_Filesystem_Base
*/
private $wp_filesystem;
private $error_messages = []; // Initialize as empty array
/**
* Error messages with translation
*
* @var array
*/
private $error_messages = [];
/**
* Constructor
*/
public function __construct() {
global $wp_filesystem;
if (empty($wp_filesystem)) {
@ -32,12 +49,28 @@ class BPI_Installer {
];
}
/**
* Get error message by code
*
* @param int $code Error code
* @param string $default_message Default message if code not found
* @return string Error message
*/
public function get_error_message($code, $default_message) {
return __($this->error_messages[$code] ?? $default_message, 'bulk-plugin-installer');
return isset($this->error_messages[$code]) ? $this->error_messages[$code] : $default_message;
}
/**
* Check if a ZIP file is a plugin
*
* @param string $zip_path Path to ZIP file
* @return bool True if it's a plugin, false otherwise
*/
private function is_plugin_zip($zip_path) {
if (!extension_loaded('zip')) {
return false;
}
$zip = new ZipArchive();
if ($zip->open($zip_path) === true) {
for ($i = 0; $i < $zip->numFiles; $i++) {
@ -55,7 +88,17 @@ class BPI_Installer {
return false;
}
/**
* Check if a ZIP file is a theme
*
* @param string $zip_path Path to ZIP file
* @return bool True if it's a theme, false otherwise
*/
private function is_theme_zip($zip_path) {
if (!extension_loaded('zip')) {
return false;
}
$zip = new ZipArchive();
if ($zip->open($zip_path) === true) {
for ($i = 0; $i < $zip->numFiles; $i++) {
@ -69,6 +112,13 @@ class BPI_Installer {
return false;
}
/**
* Install plugins
*
* @param array $items Items to install
* @param string $type Installation type
* @return array Installation results
*/
public function bpi_install_plugins($items, $type) {
$valid_types = ['repository', 'wenpai', 'url', 'upload'];
if (!in_array($type, $valid_types)) {
@ -83,6 +133,13 @@ class BPI_Installer {
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
require_once ABSPATH . 'wp-admin/includes/plugin.php';
// Get installation options
$install_options = get_option('bpi_install_options', [
'duplicate_handling' => 'skip',
'auto_activate' => false,
'keep_backups' => false,
]);
$results = [];
$upgrader = new Plugin_Upgrader(new WP_Ajax_Upgrader_Skin());
$installed_plugins = get_plugins();
@ -105,12 +162,74 @@ 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'),
'skipped' => true
];
continue;
switch ($install_options['duplicate_handling']) {
case 'skip':
$results[$item] = [
'success' => true,
'message' => __('Plugin already installed, skipped', 'bulk-plugin-installer'),
'skipped' => true
];
bpi_add_log_entry(
$item,
'plugin',
'install',
$type,
'skipped',
__('Plugin already installed, skipped', 'bulk-plugin-installer')
);
continue 2; // Skip to the next item in the foreach loop
case 'reinstall':
// Continue with reinstall (don't add any special code here, just don't skip)
if ($install_options['keep_backups']) {
// Backup the existing plugin
$backup_dir = WP_CONTENT_DIR . '/bpi-backups/plugins/';
if (!file_exists($backup_dir)) {
wp_mkdir_p($backup_dir);
}
$plugin_dir = WP_PLUGIN_DIR . '/' . dirname($plugin_key);
$backup_path = $backup_dir . basename($plugin_dir) . '-' . date('Y-m-d-H-i-s') . '.zip';
// Create backup
if (class_exists('ZipArchive')) {
$zip = new ZipArchive();
if ($zip->open($backup_path, ZipArchive::CREATE) === TRUE) {
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($plugin_dir),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($files as $file) {
if (!$file->isDir()) {
$filePath = $file->getRealPath();
$relativePath = substr($filePath, strlen($plugin_dir) + 1);
$zip->addFile($filePath, $relativePath);
}
}
$zip->close();
}
}
}
break;
case 'error':
$results[$item] = [
'success' => false,
'message' => __('Plugin already installed. Set "Skip duplicates" in settings to ignore this error.', 'bulk-plugin-installer'),
'error_code' => 1013
];
bpi_add_log_entry(
$item,
'plugin',
'install',
$type,
'error',
__('Plugin already installed. Set "Skip duplicates" in settings to ignore this error.', 'bulk-plugin-installer')
);
continue 2; // Skip to the next item in the foreach loop
}
}
$download_link = '';
@ -225,9 +344,47 @@ class BPI_Installer {
);
}
$success_message = __('Successfully installed', 'bulk-plugin-installer');
// Add auto-activation logic
if ($install_options['auto_activate'] && $result === true) {
$plugin_file = false;
// Find the installed plugin file
$plugin_folders = glob(WP_PLUGIN_DIR . '/*', GLOB_ONLYDIR);
foreach ($plugin_folders as $plugin_folder) {
$potential_main_file = basename($plugin_folder) . '.php';
if (file_exists($plugin_folder . '/' . $potential_main_file)) {
$plugin_file = basename($plugin_folder) . '/' . $potential_main_file;
break;
}
}
if ($plugin_file) {
if (is_multisite() && is_network_admin()) {
$activate = activate_plugin($plugin_file, '', true);
} else {
$activate = activate_plugin($plugin_file);
}
if (!is_wp_error($activate)) {
$success_message .= ' ' . __('and activated', 'bulk-plugin-installer');
}
}
}
bpi_add_log_entry(
$item,
'plugin',
'install',
$type,
'success',
$success_message
);
$results[$item] = [
'success' => true,
'message' => __('Successfully installed', 'bulk-plugin-installer')
'message' => $success_message
];
} catch (Exception $e) {
$no_retry_codes = [1001, 1004, 1005, 1007, 1009, 1010];
@ -237,12 +394,28 @@ class BPI_Installer {
'error_code' => $e->getCode(),
'retry' => !in_array($e->getCode(), $no_retry_codes)
];
bpi_add_log_entry(
$item,
'plugin',
'install',
$type,
'error',
$this->get_error_message($e->getCode(), $e->getMessage())
);
}
}
return $results;
}
/**
* Install themes
*
* @param array $items Items to install
* @param string $type Installation type
* @return array Installation results
*/
public function bpi_install_themes($items, $type) {
$valid_types = ['repository', 'wenpai', 'url', 'upload'];
if (!in_array($type, $valid_types)) {
@ -256,6 +429,12 @@ class BPI_Installer {
require_once ABSPATH . 'wp-admin/includes/theme-install.php';
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
$install_options = get_option('bpi_install_options', [
'duplicate_handling' => 'skip',
'auto_activate' => false,
'keep_backups' => false,
]);
$results = [];
$upgrader = new Theme_Upgrader(new WP_Ajax_Upgrader_Skin());
$installed_themes = wp_get_themes();
@ -299,6 +478,21 @@ class BPI_Installer {
);
}
if (!$this->is_theme_zip($dest_path)) {
if ($this->is_plugin_zip($dest_path)) {
unlink($dest_path);
throw new Exception(
$this->get_error_message(2005, 'Plugin ZIP detected'),
2005
);
}
unlink($dest_path);
throw new Exception(
$this->get_error_message(2006, 'Invalid theme ZIP'),
2006
);
}
$zip = new ZipArchive();
if ($zip->open($dest_path) === true) {
$theme_key = null;
@ -316,30 +510,80 @@ class BPI_Installer {
}
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', 'bulk-plugin-installer'),
'skipped' => true
];
continue;
}
switch ($install_options['duplicate_handling']) {
case 'skip':
if (file_exists($dest_path)) {
unlink($dest_path);
}
$results[$file_name] = [
'success' => true,
'message' => __('Theme already installed, skipped', 'bulk-plugin-installer'),
'skipped' => true
];
bpi_add_log_entry(
$file_name,
'theme',
'install',
$type,
'skipped',
__('Theme already installed, skipped', 'bulk-plugin-installer')
);
continue 2; // Skip to the next item in the foreach loop
if (!$this->is_theme_zip($dest_path)) {
if ($this->is_plugin_zip($dest_path)) {
unlink($dest_path);
throw new Exception(
$this->get_error_message(2005, 'Plugin ZIP detected'),
2005
);
case 'reinstall':
// Continue with reinstall
if ($install_options['keep_backups']) {
// Backup code for themes
$backup_dir = WP_CONTENT_DIR . '/bpi-backups/themes/';
if (!file_exists($backup_dir)) {
wp_mkdir_p($backup_dir);
}
$theme_dir = get_theme_root() . '/' . $theme_key;
$backup_path = $backup_dir . $theme_key . '-' . date('Y-m-d-H-i-s') . '.zip';
// Create backup
if (class_exists('ZipArchive') && file_exists($theme_dir)) {
$zip = new ZipArchive();
if ($zip->open($backup_path, ZipArchive::CREATE) === TRUE) {
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($theme_dir),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($files as $file) {
if (!$file->isDir()) {
$filePath = $file->getRealPath();
$relativePath = substr($filePath, strlen($theme_dir) + 1);
$zip->addFile($filePath, $relativePath);
}
}
$zip->close();
}
}
}
break;
case 'error':
if (file_exists($dest_path)) {
unlink($dest_path);
}
$results[$file_name] = [
'success' => false,
'message' => __('Theme already installed. Set "Skip duplicates" in settings to ignore this error.', 'bulk-plugin-installer'),
'error_code' => 2013
];
bpi_add_log_entry(
$file_name,
'theme',
'install',
$type,
'error',
__('Theme already installed. Set "Skip duplicates" in settings to ignore this error.', 'bulk-plugin-installer')
);
continue 2; // Skip to the next item in the foreach loop
}
unlink($dest_path);
throw new Exception(
$this->get_error_message(2006, 'Invalid theme ZIP'),
2006
);
}
$download_link = $dest_path;
@ -347,12 +591,74 @@ class BPI_Installer {
} else {
$theme_key = $item;
if ($theme_key && array_key_exists($theme_key, $installed_themes)) {
$results[$item] = [
'success' => true,
'message' => __('Theme already installed, skipped', 'bulk-plugin-installer'),
'skipped' => true
];
continue;
switch ($install_options['duplicate_handling']) {
case 'skip':
$results[$item] = [
'success' => true,
'message' => __('Theme already installed, skipped', 'bulk-plugin-installer'),
'skipped' => true
];
bpi_add_log_entry(
$item,
'theme',
'install',
$type,
'skipped',
__('Theme already installed, skipped', 'bulk-plugin-installer')
);
continue 2; // Skip to the next item in the foreach loop
case 'reinstall':
// Continue with reinstall
if ($install_options['keep_backups']) {
// Backup code for themes
$backup_dir = WP_CONTENT_DIR . '/bpi-backups/themes/';
if (!file_exists($backup_dir)) {
wp_mkdir_p($backup_dir);
}
$theme_dir = get_theme_root() . '/' . $theme_key;
$backup_path = $backup_dir . $theme_key . '-' . date('Y-m-d-H-i-s') . '.zip';
// Create backup
if (class_exists('ZipArchive') && file_exists($theme_dir)) {
$zip = new ZipArchive();
if ($zip->open($backup_path, ZipArchive::CREATE) === TRUE) {
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($theme_dir),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($files as $file) {
if (!$file->isDir()) {
$filePath = $file->getRealPath();
$relativePath = substr($filePath, strlen($theme_dir) + 1);
$zip->addFile($filePath, $relativePath);
}
}
$zip->close();
}
}
}
break;
case 'error':
$results[$item] = [
'success' => false,
'message' => __('Theme already installed. Set "Skip duplicates" in settings to ignore this error.', 'bulk-plugin-installer'),
'error_code' => 2013
];
bpi_add_log_entry(
$item,
'theme',
'install',
$type,
'error',
__('Theme already installed. Set "Skip duplicates" in settings to ignore this error.', 'bulk-plugin-installer')
);
continue 2; // Skip to the next item in the foreach loop
}
}
}
@ -426,6 +732,15 @@ class BPI_Installer {
);
}
bpi_add_log_entry(
$item,
'theme',
'install',
$type,
'success',
__('Successfully installed', 'bulk-plugin-installer')
);
$results[$item] = [
'success' => true,
'message' => __('Successfully installed', 'bulk-plugin-installer')
@ -438,6 +753,15 @@ class BPI_Installer {
'error_code' => $e->getCode(),
'retry' => !in_array($e->getCode(), $no_retry_codes)
];
bpi_add_log_entry(
$item,
'theme',
'install',
$type,
'error',
$this->get_error_message($e->getCode(), $e->getMessage())
);
}
}

489
includes/collections.php Normal file
View file

@ -0,0 +1,489 @@
<?php
/**
* Functions for handling collections
*/
/**
* Get preset plugin collections
*
* @param bool $force_refresh Force refreshing cache
* @return array Collections data
*/
function bpi_get_preset_collections($force_refresh = false) {
$collections = get_transient('bpi_preset_collections');
if ($force_refresh || false === $collections) {
$collections = [
'version' => '1.0',
'last_updated' => current_time('Y-m-d'),
'collections' => []
];
// First try to load from remote sources
$sources = get_option('bpi_collection_sources', []);
$loaded_remote = false;
foreach ($sources as $source) {
if (empty($source['enabled']) || empty($source['url'])) {
continue;
}
$response = wp_remote_get($source['url'], [
'timeout' => 15,
'sslverify' => true,
'headers' => [
'User-Agent' => 'WordPress/Bulk-Plugin-Installer-' . BPI_VERSION
]
]);
if (!is_wp_error($response) && 200 === wp_remote_retrieve_response_code($response)) {
$remote_data = json_decode(wp_remote_retrieve_body($response), true);
if (is_array($remote_data) && !empty($remote_data['collections'])) {
if (isset($remote_data['version']) && version_compare($remote_data['version'], $collections['version'], '>')) {
$collections['version'] = $remote_data['version'];
}
if (isset($remote_data['last_updated'])) {
$collections['last_updated'] = $remote_data['last_updated'];
}
$collections['collections'] = array_merge($collections['collections'], $remote_data['collections']);
$loaded_remote = true;
}
}
}
// If no remote collections were loaded, try local file
if (!$loaded_remote) {
$local_file = BPI_PATH . 'data/collections.json';
if (file_exists($local_file)) {
$local_data = json_decode(file_get_contents($local_file), true);
if (is_array($local_data) && !empty($local_data['collections'])) {
$collections = $local_data;
}
}
}
// Cache for 24 hours
set_transient('bpi_preset_collections', $collections, DAY_IN_SECONDS);
}
return apply_filters('bpi_preset_collections', $collections);
}
/**
* Count items in a collection
*
* @param array $items Collection items
* @return int Total count
*/
function bpi_count_collection_items($items) {
$count = 0;
if (!is_array($items)) {
return 0;
}
foreach ($items as $source => $source_items) {
if (is_array($source_items)) {
$count += count($source_items);
}
}
return $count;
}
/**
* Get AJAX handler for collection details
*/
function bpi_ajax_get_collection_details() {
check_ajax_referer('bpi_installer', 'nonce');
if (!bpi_user_can_install()) {
wp_send_json_error([
'message' => __('Insufficient permissions', 'bulk-plugin-installer')
]);
}
$collection_id = sanitize_text_field($_POST['collection_id'] ?? '');
$collections = bpi_get_preset_collections();
if (!isset($collections['collections'][$collection_id])) {
wp_send_json_error([
'message' => __('Collection not found', 'bulk-plugin-installer')
]);
}
$collection = $collections['collections'][$collection_id];
// Generate HTML output
ob_start();
?>
<h2><?php echo esc_html($collection['name']); ?></h2>
<p class="bpi-collection-description"><?php echo esc_html($collection['description']); ?></p>
<?php if (!empty($collection['screenshot'])) : ?>
<div class="bpi-collection-screenshot">
<img src="<?php echo esc_url($collection['screenshot']); ?>" alt="<?php echo esc_attr($collection['name']); ?>">
</div>
<?php endif; ?>
<div class="bpi-install-collection-wrapper">
<p><?php printf(__('This collection includes %d plugins and %d themes. Click the button to install them all.', 'bulk-plugin-installer'),
bpi_count_collection_items($collection['plugins'] ?? []),
bpi_count_collection_items($collection['themes'] ?? [])
); ?></p>
<button type="button" class="button button-primary bpi-install-collection" data-collection="<?php echo esc_attr($collection_id); ?>">
<?php _e('Install Collection', 'bulk-plugin-installer'); ?>
</button>
</div>
<?php if (!empty($collection['plugins'])) : ?>
<div class="bpi-collection-plugins">
<h3><?php _e('Plugins', 'bulk-plugin-installer'); ?></h3>
<div class="bpi-collection-item-list">
<?php foreach ($collection['plugins'] as $source => $plugins) : ?>
<?php if (!is_array($plugins)) continue; ?>
<?php foreach ($plugins as $plugin) :
$slug = is_array($plugin) ? $plugin['slug'] : $plugin;
$name = is_array($plugin) ? $plugin['name'] : $slug;
$description = is_array($plugin) ? ($plugin['description'] ?? '') : '';
$required = is_array($plugin) && isset($plugin['required']) ? $plugin['required'] : false;
?>
<div class="bpi-collection-item">
<div class="bpi-collection-item-icon">
<span class="dashicons dashicons-admin-plugins"></span>
</div>
<div class="bpi-collection-item-name">
<?php echo esc_html($name); ?>
<?php if ($required) : ?>
<span class="bpi-required"><?php _e('(Required)', 'bulk-plugin-installer'); ?></span>
<?php endif; ?>
<?php if ($description) : ?>
<div class="bpi-collection-item-description"><?php echo esc_html($description); ?></div>
<?php endif; ?>
</div>
<div class="bpi-collection-item-source">
<?php
switch ($source) {
case 'repository':
_e('Repository', 'bulk-plugin-installer');
break;
case 'wenpai':
_e('WenPai', 'bulk-plugin-installer');
break;
case 'url':
_e('URL', 'bulk-plugin-installer');
break;
}
?>
</div>
</div>
<?php endforeach; ?>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php if (!empty($collection['themes'])) : ?>
<div class="bpi-collection-themes">
<h3><?php _e('Themes', 'bulk-plugin-installer'); ?></h3>
<div class="bpi-collection-item-list">
<?php foreach ($collection['themes'] as $source => $themes) : ?>
<?php if (!is_array($themes)) continue; ?>
<?php foreach ($themes as $theme) :
$slug = is_array($theme) ? $theme['slug'] : $theme;
$name = is_array($theme) ? $theme['name'] : $slug;
$description = is_array($theme) ? ($theme['description'] ?? '') : '';
$required = is_array($theme) && isset($theme['required']) ? $theme['required'] : false;
?>
<div class="bpi-collection-item">
<div class="bpi-collection-item-icon">
<span class="dashicons dashicons-admin-appearance"></span>
</div>
<div class="bpi-collection-item-name">
<?php echo esc_html($name); ?>
<?php if ($required) : ?>
<span class="bpi-required"><?php _e('(Required)', 'bulk-plugin-installer'); ?></span>
<?php endif; ?>
<?php if ($description) : ?>
<div class="bpi-collection-item-description"><?php echo esc_html($description); ?></div>
<?php endif; ?>
</div>
<div class="bpi-collection-item-source">
<?php
switch ($source) {
case 'repository':
_e('Repository', 'bulk-plugin-installer');
break;
case 'wenpai':
_e('WenPai', 'bulk-plugin-installer');
break;
case 'url':
_e('URL', 'bulk-plugin-installer');
break;
}
?>
</div>
</div>
<?php endforeach; ?>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php
$html = ob_get_clean();
wp_send_json_success([
'html' => $html,
'name' => $collection['name']
]);
}
/**
* Install collection AJAX handler
*/
function bpi_ajax_install_collection() {
check_ajax_referer('bpi_installer', 'nonce');
if (!bpi_user_can_install()) {
wp_send_json_error([
'message' => __('Insufficient permissions', 'bulk-plugin-installer')
]);
}
$collection_id = sanitize_text_field($_POST['collection_id'] ?? '');
$collections = bpi_get_preset_collections();
if (!isset($collections['collections'][$collection_id])) {
wp_send_json_error([
'message' => __('Collection not found', 'bulk-plugin-installer')
]);
}
$collection = $collections['collections'][$collection_id];
$installer = new BPI_Installer();
$results = [
'plugins' => [],
'themes' => []
];
// Install plugins
if (!empty($collection['plugins']) && is_array($collection['plugins'])) {
foreach ($collection['plugins'] as $source => $plugins) {
if (!is_array($plugins) || empty($plugins)) {
continue;
}
$items = [];
foreach ($plugins as $plugin) {
$items[] = is_array($plugin) ? $plugin['slug'] : $plugin;
}
if (!empty($items)) {
$source_results = $installer->bpi_install_plugins($items, $source);
$results['plugins'] = array_merge($results['plugins'], $source_results);
}
}
}
// Install themes
if (!empty($collection['themes']) && is_array($collection['themes'])) {
foreach ($collection['themes'] as $source => $themes) {
if (!is_array($themes) || empty($themes)) {
continue;
}
$items = [];
foreach ($themes as $theme) {
$items[] = is_array($theme) ? $theme['slug'] : $theme;
}
if (!empty($items)) {
$source_results = $installer->bpi_install_themes($items, $source);
$results['themes'] = array_merge($results['themes'], $source_results);
}
}
}
// Update statistics
bpi_update_statistics(array_merge($results['plugins'], $results['themes']));
// Generate HTML output
ob_start();
?>
<div class="notice notice-success">
<p><?php printf(__('Installation of collection "%s" completed!', 'bulk-plugin-installer'), esc_html($collection['name'])); ?></p>
</div>
<h3><?php _e('Plugins Results', 'bulk-plugin-installer'); ?></h3>
<?php if (empty($results['plugins'])) : ?>
<p><?php _e('No plugins were installed.', 'bulk-plugin-installer'); ?></p>
<?php else : ?>
<ul class="installation-list">
<?php foreach ($results['plugins'] as $item => $result) : ?>
<li class="<?php echo $result['success'] ? 'success' : 'error'; ?>">
<span class="item-name"><?php echo esc_html($item); ?></span>
<span class="status <?php echo $result['success'] ? 'success' : 'error'; ?>">
<?php if ($result['success']) : ?>
<?php echo isset($result['skipped']) && $result['skipped'] ? 'ⓘ ' : '✓ '; ?>
<?php echo esc_html($result['message']); ?>
<?php else : ?>
<?php echo esc_html($result['message']); ?>
<?php if (isset($result['retry']) && $result['retry']) : ?>
<button class="retry-btn" data-item="<?php echo esc_attr($item); ?>" data-type="repository">
<?php _e('Retry', 'bulk-plugin-installer'); ?>
</button>
<?php endif; ?>
<?php endif; ?>
</span>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<h3><?php _e('Themes Results', 'bulk-plugin-installer'); ?></h3>
<?php if (empty($results['themes'])) : ?>
<p><?php _e('No themes were installed.', 'bulk-plugin-installer'); ?></p>
<?php else : ?>
<ul class="installation-list">
<?php foreach ($results['themes'] as $item => $result) : ?>
<li class="<?php echo $result['success'] ? 'success' : 'error'; ?>">
<span class="item-name"><?php echo esc_html($item); ?></span>
<span class="status <?php echo $result['success'] ? 'success' : 'error'; ?>">
<?php if ($result['success']) : ?>
<?php echo isset($result['skipped']) && $result['skipped'] ? 'ⓘ ' : '✓ '; ?>
<?php echo esc_html($result['message']); ?>
<?php else : ?>
<?php echo esc_html($result['message']); ?>
<?php if (isset($result['retry']) && $result['retry']) : ?>
<button class="retry-btn" data-item="<?php echo esc_attr($item); ?>" data-type="repository">
<?php _e('Retry', 'bulk-plugin-installer'); ?>
</button>
<?php endif; ?>
<?php endif; ?>
</span>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<?php if (isset($collection['configuration'])) : ?>
<div class="notice notice-info">
<p><?php _e('Collection installed successfully. Additional configuration is available.', 'bulk-plugin-installer'); ?></p>
<p><button class="button button-secondary bpi-configure-collection" data-collection="<?php echo esc_attr($collection_id); ?>">
<?php _e('Configure Site', 'bulk-plugin-installer'); ?>
</button></p>
</div>
<?php endif; ?>
<?php
$html = ob_get_clean();
wp_send_json_success([
'html' => $html,
'results' => $results
]);
}
/**
* Save remote collection source
*/
function bpi_ajax_save_remote_source() {
check_ajax_referer('bpi_installer', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error([
'message' => __('Insufficient permissions', 'bulk-plugin-installer')
]);
}
$name = sanitize_text_field($_POST['name'] ?? '');
$url = esc_url_raw($_POST['url'] ?? '');
$id = sanitize_text_field($_POST['id'] ?? '');
if (empty($url) || !filter_var($url, FILTER_VALIDATE_URL)) {
wp_send_json_error([
'message' => __('Please enter a valid URL', 'bulk-plugin-installer')
]);
}
$sources = get_option('bpi_collection_sources', []);
// Check if URL is valid and accessible
$response = wp_remote_get($url, [
'timeout' => 15,
'sslverify' => true,
'headers' => [
'User-Agent' => 'WordPress/Bulk-Plugin-Installer-' . BPI_VERSION
]
]);
if (is_wp_error($response)) {
wp_send_json_error([
'message' => __('Unable to access URL. Error: ', 'bulk-plugin-installer') . $response->get_error_message()
]);
}
$response_code = wp_remote_retrieve_response_code($response);
if ($response_code !== 200) {
wp_send_json_error([
'message' => sprintf(__('Invalid response from URL. Status code: %d', 'bulk-plugin-installer'), $response_code)
]);
}
$json = json_decode(wp_remote_retrieve_body($response), true);
if (!is_array($json) || !isset($json['collections']) || !is_array($json['collections'])) {
wp_send_json_error([
'message' => __('The URL does not contain valid collections data', 'bulk-plugin-installer')
]);
}
if (empty($id)) {
// Add new source
$sources[] = [
'name' => !empty($name) ? $name : sprintf(__('Collection Source %d', 'bulk-plugin-installer'), count($sources) + 1),
'url' => $url,
'enabled' => true
];
} else {
// Update existing source
foreach ($sources as $index => $source) {
if ($index == $id) {
$sources[$index]['name'] = !empty($name) ? $name : $source['name'];
$sources[$index]['url'] = $url;
break;
}
}
}
update_option('bpi_collection_sources', $sources);
delete_transient('bpi_preset_collections');
wp_send_json_success([
'message' => __('Collection source saved successfully', 'bulk-plugin-installer'),
'sources' => $sources
]);
}
/**
* Refresh collections
*/
function bpi_ajax_refresh_collections() {
check_ajax_referer('bpi_installer', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error([
'message' => __('Insufficient permissions', 'bulk-plugin-installer')
]);
}
delete_transient('bpi_preset_collections');
$collections = bpi_get_preset_collections(true);
wp_send_json_success([
'message' => __('Collections refreshed successfully', 'bulk-plugin-installer'),
'count' => count($collections['collections']),
'last_updated' => $collections['last_updated']
]);
}

275
includes/logging.php Normal file
View file

@ -0,0 +1,275 @@
<?php
/**
* Logging functions for Bulk Plugin Installer
*/
/**
* Create logs table
*/
function bpi_create_logs_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'bpi_logs';
$charset_collate = $wpdb->get_charset_collate();
if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
$sql = "CREATE TABLE $table_name (
id bigint(20) NOT NULL AUTO_INCREMENT,
log_time datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
item_name varchar(255) NOT NULL,
item_type varchar(50) NOT NULL,
action varchar(50) NOT NULL,
source varchar(100) NOT NULL,
status varchar(50) NOT NULL,
message text NOT NULL,
user_id bigint(20) NOT NULL,
PRIMARY KEY (id),
KEY log_time (log_time),
KEY item_type (item_type),
KEY status (status)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
}
}
/**
* Add log entry
*
* @param string $item_name Item name
* @param string $item_type Item type (plugin or theme)
* @param string $action Action (install, update, delete)
* @param string $source Source (repository, wenpai, url, upload)
* @param string $status Status (success, error, skipped)
* @param string $message Message
* @return int|false The log ID or false on failure
*/
function bpi_add_log_entry($item_name, $item_type, $action, $source, $status, $message) {
global $wpdb;
$table_name = $wpdb->prefix . 'bpi_logs';
return $wpdb->insert(
$table_name,
[
'log_time' => current_time('mysql'),
'item_name' => $item_name,
'item_type' => $item_type,
'action' => $action,
'source' => $source,
'status' => $status,
'message' => $message,
'user_id' => get_current_user_id()
],
[
'%s', // log_time
'%s', // item_name
'%s', // item_type
'%s', // action
'%s', // source
'%s', // status
'%s', // message
'%d' // user_id
]
);
}
/**
* Get logs
*
* @param array $filters Filters
* @param int $per_page Items per page
* @param int $page Current page
* @return array Logs data
*/
function bpi_get_logs($filters = [], $per_page = 20, $page = 1) {
global $wpdb;
$table_name = $wpdb->prefix . 'bpi_logs';
$where = [];
$prepare = [];
if (!empty($filters['item_type'])) {
$where[] = 'item_type = %s';
$prepare[] = $filters['item_type'];
}
if (!empty($filters['status'])) {
$where[] = 'status = %s';
$prepare[] = $filters['status'];
}
if (!empty($filters['date_from'])) {
$where[] = 'log_time >= %s';
$prepare[] = $filters['date_from'] . ' 00:00:00';
}
if (!empty($filters['date_to'])) {
$where[] = 'log_time <= %s';
$prepare[] = $filters['date_to'] . ' 23:59:59';
}
if (!empty($filters['search'])) {
$where[] = '(item_name LIKE %s OR message LIKE %s)';
$search_term = '%' . $wpdb->esc_like($filters['search']) . '%';
$prepare[] = $search_term;
$prepare[] = $search_term;
}
$where_clause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
$offset = ($page - 1) * $per_page;
$query = $wpdb->prepare(
"SELECT * FROM $table_name $where_clause ORDER BY log_time DESC LIMIT %d OFFSET %d",
array_merge($prepare, [$per_page, $offset])
);
$logs = $wpdb->get_results($query);
// Get total count for pagination
$count_query = $wpdb->prepare(
"SELECT COUNT(*) FROM $table_name $where_clause",
$prepare
);
$total = $wpdb->get_var($count_query);
return [
'logs' => $logs,
'total' => $total,
'pages' => ceil($total / $per_page)
];
}
/**
* Clear logs
*/
function bpi_clear_logs() {
global $wpdb;
$table_name = $wpdb->prefix . 'bpi_logs';
$wpdb->query("TRUNCATE TABLE $table_name");
}
/**
* Handle clear logs request
*/
function bpi_handle_clear_logs() {
if (isset($_POST['action']) && $_POST['action'] === 'bpi_clear_logs' && isset($_POST['bpi_logs_nonce']) && wp_verify_nonce($_POST['bpi_logs_nonce'], 'bpi_clear_logs')) {
if (!current_user_can('manage_options')) {
wp_die(__('You do not have sufficient permissions to access this page.', 'bulk-plugin-installer'));
}
bpi_clear_logs();
wp_redirect(add_query_arg(['page' => 'bulk-plugin-installer', 'tab' => 'logs', 'cleared' => '1'], admin_url('plugins.php')));
exit;
}
}
add_action('admin_init', 'bpi_handle_clear_logs');
/**
* Handle export logs request
*/
function bpi_handle_export_logs() {
if (isset($_GET['action']) && $_GET['action'] === 'bpi_export_logs' && isset($_GET['bpi_logs_nonce']) && wp_verify_nonce($_GET['bpi_logs_nonce'], 'bpi_export_logs')) {
if (!current_user_can('manage_options')) {
wp_die(__('You do not have sufficient permissions to access this page.', 'bulk-plugin-installer'));
}
global $wpdb;
$table_name = $wpdb->prefix . 'bpi_logs';
$logs = $wpdb->get_results("SELECT * FROM $table_name ORDER BY log_time DESC");
// Set headers for CSV download
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename=bpi-logs-' . date('Y-m-d') . '.csv');
// Create output stream
$output = fopen('php://output', 'w');
// Add BOM for UTF-8
fprintf($output, chr(0xEF).chr(0xBB).chr(0xBF));
// Add headers
fputcsv($output, [
__('ID', 'bulk-plugin-installer'),
__('Time', 'bulk-plugin-installer'),
__('Item', 'bulk-plugin-installer'),
__('Type', 'bulk-plugin-installer'),
__('Action', 'bulk-plugin-installer'),
__('Source', 'bulk-plugin-installer'),
__('Status', 'bulk-plugin-installer'),
__('Message', 'bulk-plugin-installer'),
__('User ID', 'bulk-plugin-installer'),
__('Username', 'bulk-plugin-installer'),
]);
// Add data rows
foreach ($logs as $log) {
$user = get_userdata($log->user_id);
$username = $user ? $user->user_login : __('Unknown', 'bulk-plugin-installer');
fputcsv($output, [
$log->id,
$log->log_time,
$log->item_name,
$log->item_type,
$log->action,
$log->source,
$log->status,
$log->message,
$log->user_id,
$username
]);
}
fclose($output);
exit;
}
}
add_action('admin_post_bpi_export_logs', 'bpi_handle_export_logs');
/**
* Log WordPress core plugin/theme installations
*/
function bpi_log_wp_installations() {
add_action('upgrader_process_complete', function($upgrader, $hook_extra) {
if (isset($hook_extra['type']) && isset($hook_extra['action'])) {
// For plugins
if ($hook_extra['type'] === 'plugin' && $hook_extra['action'] === 'install') {
$plugin_info = $upgrader->plugin_info();
if ($plugin_info) {
$plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin_info);
$plugin_name = $plugin_data['Name'] ?? basename($plugin_info, '.php');
bpi_add_log_entry(
$plugin_name,
'plugin',
'install',
'wordpress_core',
'success',
__('Plugin installed via WordPress core installer', 'bulk-plugin-installer')
);
}
}
// For themes
if ($hook_extra['type'] === 'theme' && $hook_extra['action'] === 'install') {
$theme_info = $upgrader->theme_info();
if ($theme_info) {
$theme_name = $theme_info->get('Name');
bpi_add_log_entry(
$theme_name,
'theme',
'install',
'wordpress_core',
'success',
__('Theme installed via WordPress core installer', 'bulk-plugin-installer')
);
}
}
}
}, 10, 2);
}
add_action('init', 'bpi_log_wp_installations');

451
includes/reinstaller.php Normal file
View file

@ -0,0 +1,451 @@
<?php
/**
* Functions for handling reinstallation operations
*/
/**
* Check if a plugin exists in the WordPress.org repository
*
* @param string $slug Plugin slug
* @return bool True if exists, false otherwise
*/
function bpi_plugin_exists_in_repository($slug) {
if (empty($slug)) {
return false;
}
$url = 'https://api.wordpress.org/plugins/info/1.0/' . $slug . '.json';
$response = wp_remote_get($url);
if (is_wp_error($response)) {
return false;
}
$code = wp_remote_retrieve_response_code($response);
if ($code !== 200) {
return false;
}
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
return !empty($data) && isset($data['slug']);
}
/**
* Check if a theme exists in the WordPress.org repository
*
* @param string $slug Theme slug
* @return bool True if exists, false otherwise
*/
function bpi_theme_exists_in_repository($slug) {
if (empty($slug)) {
return false;
}
$url = 'https://api.wordpress.org/themes/info/1.1/?action=theme_information&request[slug]=' . $slug;
$response = wp_remote_get($url);
if (is_wp_error($response)) {
return false;
}
$code = wp_remote_retrieve_response_code($response);
if ($code !== 200) {
return false;
}
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
return !empty($data) && isset($data['slug']);
}
/**
* Get all installed plugins
*
* @return array Installed plugins information
*/
function bpi_get_installed_plugins() {
if (!function_exists('get_plugins')) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$all_plugins = get_plugins();
$installed_plugins = [];
// Create a cache for repository existence checks
$repository_check_cache = [];
foreach ($all_plugins as $plugin_path => $plugin_data) {
$plugin_slug = dirname($plugin_path);
if ($plugin_slug === '.') {
// Handle single-file plugins
$plugin_slug = basename($plugin_path, '.php');
}
// Default source
$source = 'unknown';
// Get plugin status
if (is_plugin_active($plugin_path)) {
$status = 'active';
} else {
$status = 'inactive';
}
// Source determination logic
// 1. Check common WordPress.org plugins first
$common_wp_plugins = [
'akismet', 'hello', 'contact-form-7', 'woocommerce', 'wordpress-seo',
'jetpack', 'wordfence', 'elementor', 'all-in-one-seo-pack', 'wp-super-cache'
];
if (in_array($plugin_slug, $common_wp_plugins)) {
$source = 'repository';
} else {
// 2. Check plugin/author URI for wordpress.org references
if (
strpos($plugin_data['PluginURI'], 'wordpress.org') !== false ||
strpos($plugin_data['AuthorURI'], 'wordpress.org') !== false
) {
$source = 'repository';
}
// 3. Check for WenPai sources
elseif (
strpos($plugin_data['PluginURI'], 'wenpai.net') !== false ||
strpos($plugin_data['PluginURI'], 'wenpai.org') !== false ||
strpos($plugin_data['PluginURI'], 'wenpai.cn') !== false ||
strpos($plugin_data['AuthorURI'], 'wenpai.net') !== false ||
strpos($plugin_data['AuthorURI'], 'wenpai.org') !== false ||
strpos($plugin_data['AuthorURI'], 'wenpai.cn') !== false
) {
$source = 'wenpai';
}
// 4. Check if plugin exists in WordPress.org repository
elseif (!isset($repository_check_cache[$plugin_slug])) {
$repository_check_cache[$plugin_slug] = bpi_plugin_exists_in_repository($plugin_slug);
if ($repository_check_cache[$plugin_slug]) {
$source = 'repository';
}
} elseif ($repository_check_cache[$plugin_slug]) {
$source = 'repository';
}
}
$installed_plugins[] = [
'name' => $plugin_data['Name'],
'slug' => $plugin_slug,
'path' => $plugin_path,
'version' => $plugin_data['Version'],
'source' => $source,
'status' => $status,
'plugin_uri' => $plugin_data['PluginURI'],
'author' => $plugin_data['Author'],
'author_uri' => $plugin_data['AuthorURI'],
'description' => $plugin_data['Description']
];
}
return $installed_plugins;
}
/**
* Get all installed themes
*
* @return array Installed themes information
*/
function bpi_get_installed_themes() {
$all_themes = wp_get_themes();
$installed_themes = [];
$active_theme = wp_get_theme();
// Create a cache for repository existence checks
$repository_check_cache = [];
foreach ($all_themes as $theme_slug => $theme_obj) {
// Default source
$source = 'unknown';
// Check if theme is active
if ($active_theme->get_stylesheet() === $theme_slug) {
$status = 'active';
} else {
$status = 'inactive';
}
// Get theme URIs
$theme_uri = $theme_obj->get('ThemeURI');
$author_uri = $theme_obj->get('AuthorURI');
// Source determination logic
// 1. Check common WordPress.org themes first
$common_wp_themes = [
'twentytwentyfour', 'twentytwentythree', 'twentytwentytwo', 'twentytwentyone',
'twentytwenty', 'twentynineteen', 'twentyeighteen', 'twentyseventeen'
];
if (in_array($theme_slug, $common_wp_themes)) {
$source = 'repository';
} else {
// 2. Check theme/author URI for wordpress.org references
if (
strpos($theme_uri, 'wordpress.org') !== false ||
strpos($author_uri, 'wordpress.org') !== false
) {
$source = 'repository';
}
// 3. Check for WenPai sources
elseif (
strpos($theme_uri, 'wenpai.net') !== false ||
strpos($theme_uri, 'wenpai.org') !== false ||
strpos($theme_uri, 'wenpai.cn') !== false ||
strpos($author_uri, 'wenpai.net') !== false ||
strpos($author_uri, 'wenpai.org') !== false ||
strpos($author_uri, 'wenpai.cn') !== false
) {
$source = 'wenpai';
}
// 4. Check if theme exists in WordPress.org repository
elseif (!isset($repository_check_cache[$theme_slug])) {
$repository_check_cache[$theme_slug] = bpi_theme_exists_in_repository($theme_slug);
if ($repository_check_cache[$theme_slug]) {
$source = 'repository';
}
} elseif ($repository_check_cache[$theme_slug]) {
$source = 'repository';
}
}
$installed_themes[] = [
'name' => $theme_obj->get('Name'),
'slug' => $theme_slug,
'version' => $theme_obj->get('Version'),
'source' => $source,
'status' => $status,
'theme_uri' => $theme_uri,
'author' => $theme_obj->get('Author'),
'author_uri' => $author_uri,
'description' => $theme_obj->get('Description')
];
}
return $installed_themes;
}
/**
* AJAX handler for getting installed plugins
*/
function bpi_ajax_get_plugins_list() {
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'));
}
$installed_plugins = bpi_get_installed_plugins();
wp_send_json_success($installed_plugins);
}
add_action('wp_ajax_bpi_get_plugins_list', 'bpi_ajax_get_plugins_list');
/**
* AJAX handler for getting installed themes
*/
function bpi_ajax_get_themes_list() {
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'));
}
$installed_themes = bpi_get_installed_themes();
wp_send_json_success($installed_themes);
}
add_action('wp_ajax_bpi_get_themes_list', 'bpi_ajax_get_themes_list');
/**
* AJAX handler for reinstalling plugins
*/
function bpi_handle_reinstall_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([
'message' => __('Insufficient permissions', 'bulk-plugin-installer'),
'error_code' => 403
]);
}
$items = isset($_POST['items']) ? json_decode(stripslashes($_POST['items']), true) : [];
if (!is_array($items) || empty($items)) {
wp_send_json_error([
'message' => __('No items provided', 'bulk-plugin-installer'),
'error_code' => 400
]);
}
require_once ABSPATH . 'wp-admin/includes/plugin.php';
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
$results = [];
$installer = new BPI_Installer();
foreach ($items as $item) {
$source = sanitize_text_field($item['source'] ?? 'repository');
$slug = sanitize_text_field($item['slug'] ?? '');
$path = sanitize_text_field($item['path'] ?? '');
$was_active = is_plugin_active($path);
if (empty($slug)) {
$results[$path] = [
'success' => false,
'message' => __('Invalid plugin slug', 'bulk-plugin-installer'),
'error_code' => 400
];
continue;
}
// Deactivate plugin first
if ($was_active) {
deactivate_plugins($path);
}
// For repository or wenpai source, use reinstaller
if ($source === 'repository' || $source === 'wenpai') {
// Use the existing installer method
$source_results = $installer->bpi_install_plugins([$slug], $source);
// Get the result for this specific item
if (isset($source_results[$slug])) {
$results[$path] = $source_results[$slug];
// If successful and the plugin was active before, try to reactivate
if ($was_active && $source_results[$slug]['success']) {
$plugin_file = WP_PLUGIN_DIR . '/' . $path;
if (file_exists($plugin_file)) {
activate_plugin($path);
$results[$path]['message'] .= ' ' . __('and reactivated', 'bulk-plugin-installer');
}
}
} else {
$results[$path] = [
'success' => false,
'message' => __('Failed to reinstall plugin', 'bulk-plugin-installer'),
'error_code' => 500
];
}
} else {
// For unknown or custom sources
$results[$path] = [
'success' => false,
'message' => __('Cannot reinstall plugin from unknown source. Please reinstall manually.', 'bulk-plugin-installer'),
'error_code' => 400,
'source' => $source
];
}
// Log the reinstallation
bpi_add_log_entry(
$slug,
'plugin',
'reinstall',
$source,
$results[$path]['success'] ? 'success' : 'error',
$results[$path]['message']
);
}
bpi_update_statistics($results);
wp_send_json_success($results);
}
add_action('wp_ajax_bpi_reinstall_plugins', 'bpi_handle_reinstall_plugins');
/**
* AJAX handler for reinstalling themes
*/
function bpi_handle_reinstall_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([
'message' => __('Insufficient permissions', 'bulk-plugin-installer'),
'error_code' => 403
]);
}
$items = isset($_POST['items']) ? json_decode(stripslashes($_POST['items']), true) : [];
if (!is_array($items) || empty($items)) {
wp_send_json_error([
'message' => __('No items provided', 'bulk-plugin-installer'),
'error_code' => 400
]);
}
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
require_once ABSPATH . 'wp-admin/includes/theme-install.php';
$results = [];
$installer = new BPI_Installer();
$active_theme = wp_get_theme();
foreach ($items as $item) {
$source = sanitize_text_field($item['source'] ?? 'repository');
$slug = sanitize_text_field($item['slug'] ?? '');
$was_active = ($active_theme->get_stylesheet() === $slug);
if (empty($slug)) {
$results[$slug] = [
'success' => false,
'message' => __('Invalid theme slug', 'bulk-plugin-installer'),
'error_code' => 400
];
continue;
}
// For repository or wenpai source, use reinstaller
if ($source === 'repository' || $source === 'wenpai') {
// Use the existing installer method
$source_results = $installer->bpi_install_themes([$slug], $source);
// Get the result for this specific item
if (isset($source_results[$slug])) {
$results[$slug] = $source_results[$slug];
// If successful and the theme was active before, try to reactivate
if ($was_active && $source_results[$slug]['success']) {
switch_theme($slug);
$results[$slug]['message'] .= ' ' . __('and reactivated', 'bulk-plugin-installer');
}
} else {
$results[$slug] = [
'success' => false,
'message' => __('Failed to reinstall theme', 'bulk-plugin-installer'),
'error_code' => 500
];
}
} else {
// For unknown or custom sources
$results[$slug] = [
'success' => false,
'message' => __('Cannot reinstall theme from unknown source. Please reinstall manually.', 'bulk-plugin-installer'),
'error_code' => 400,
'source' => $source
];
}
// Log the reinstallation
bpi_add_log_entry(
$slug,
'theme',
'reinstall',
$source,
$results[$slug]['success'] ? 'success' : 'error',
$results[$slug]['message']
);
}
bpi_update_statistics($results);
wp_send_json_success($results);
}
add_action('wp_ajax_bpi_reinstall_themes', 'bpi_handle_reinstall_themes');