mirror of
https://github.com/WenPai-org/bulk-plugin-installer.git
synced 2025-08-03 01:58:43 +08:00
dev 添加远程合集功能
This commit is contained in:
parent
7f12edb6ae
commit
93d19b6da0
9 changed files with 3884 additions and 170 deletions
|
@ -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
|
@ -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
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
// Add "Upload from URL" button to plugin install page
|
||||
// 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
|
||||
*/
|
||||
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
44
data/collections.json
Normal 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": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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' => '«',
|
||||
'next_text' => '»',
|
||||
'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"
|
||||
|
|
|
@ -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)) {
|
||||
switch ($install_options['duplicate_handling']) {
|
||||
case 'skip':
|
||||
$results[$item] = [
|
||||
'success' => true,
|
||||
'message' => __('Plugin already installed, skipped', 'bulk-plugin-installer'),
|
||||
'skipped' => true
|
||||
];
|
||||
continue;
|
||||
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,6 +510,8 @@ class BPI_Installer {
|
|||
}
|
||||
|
||||
if ($theme_key && array_key_exists($theme_key, $installed_themes)) {
|
||||
switch ($install_options['duplicate_handling']) {
|
||||
case 'skip':
|
||||
if (file_exists($dest_path)) {
|
||||
unlink($dest_path);
|
||||
}
|
||||
|
@ -324,22 +520,70 @@ class BPI_Installer {
|
|||
'message' => __('Theme already installed, skipped', 'bulk-plugin-installer'),
|
||||
'skipped' => true
|
||||
];
|
||||
continue;
|
||||
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
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
$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);
|
||||
throw new Exception(
|
||||
$this->get_error_message(2006, 'Invalid theme ZIP'),
|
||||
2006
|
||||
}
|
||||
$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
|
||||
}
|
||||
}
|
||||
|
||||
$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)) {
|
||||
switch ($install_options['duplicate_handling']) {
|
||||
case 'skip':
|
||||
$results[$item] = [
|
||||
'success' => true,
|
||||
'message' => __('Theme already installed, skipped', 'bulk-plugin-installer'),
|
||||
'skipped' => true
|
||||
];
|
||||
continue;
|
||||
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
489
includes/collections.php
Normal 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
275
includes/logging.php
Normal 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
451
includes/reinstaller.php
Normal 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');
|
Loading…
Add table
Add a link
Reference in a new issue