Last active
June 12, 2025 01:07
-
-
Save brandonjp/35848a543e5d3523940643bf3ebd7e41 to your computer and use it in GitHub Desktop.
Upload multiple WordPress plugins at once using a single ZIP file or several. You can select multiple zips. You can upload a big zip that contains multiple plugin zips or folders. You can set them all to auto-update, activate, replace existing, etc. Pretty cool! More info here: https://snipsnip.pro/s/868
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
* Plugin Name: Bulk Plugin Upload | |
* Description: Upload multiple WordPress plugins at once using a single ZIP file or several. | |
* Excerpt: Improve native WordPress plugin uploading with this Bulk Plugin Uploader which allows you to upload multiple WordPress plugins at once. You can select several individual ZIP files for upload, or choose a single ZIP that contains multiple plugins—either as separate folders or nested ZIPs. This user-friendly tool simplifies plugin management, making it quick and easy to install, activate, and set up auto-updates for your plugins. Perfect for novice users looking to streamline their WordPress site management! | |
* Version: 2.3.6 | |
* Author: brandonjp.com | |
* License: GPL v2 or later | |
* Text Domain: bulk-plugin-upload | |
* | |
* The Blog Post or CSCloud URLs will probably have the latest version. | |
* Blog Post URL: https://snipsnip.pro/s/868 | |
* CSCloud URL: https://codesnippets.cloud/snippet/brandonjp/WP-Bulk-563 | |
* Git URL: https://github.com/brandonjp/wp-bulk-plugin-uploader | |
* Gist URL: https://gist.github.com/brandonjp/35848a543e5d3523940643bf3ebd7e41 | |
* | |
* A premium "Pro" version with additional features will be available soon. | |
* Visit https://brandonjp.com/bulk-plugin-upload-pro for more information. | |
* | |
*/ | |
// Prevent direct file access | |
if (!defined('ABSPATH')) { | |
exit; | |
} | |
if (!class_exists('WP_Bulk_Plugin_Upload')) { | |
class WP_Bulk_Plugin_Upload { | |
/** @var WP_Bulk_Plugin_Upload Single instance of this class */ | |
private static $instance = null; | |
/** @var array Stores results of plugin operations */ | |
private $results = []; | |
/** @var array Plugin configuration */ | |
private $config = []; | |
/** @var array Default configuration values */ | |
private static $default_config = [ | |
// Plugin info | |
'version' => '2.3.5', | |
'text_domain' => 'bulk-plugin-upload', | |
// Menu configuration | |
'menu_parent' => 'both', // 'tools', 'plugins', or 'both' | |
'page_title' => 'Bulk Plugin Upload', | |
'menu_title' => 'Bulk Plugin Upload', | |
'capability' => 'manage_options', | |
'menu_slug' => 'bulk-plugin-upload', | |
// Form options defaults | |
'default_replace_existing' => false, | |
'default_activate_plugins' => false, | |
'default_enable_updates' => false, | |
// Interface text | |
'upload_button_text' => 'Upload and Install Plugins', | |
'replace_label' => 'Replace existing plugins', | |
'replace_description' => 'If unchecked, will skip plugins that already exist', | |
'activate_label' => 'Activate uploaded plugins', | |
'activate_description' => 'Automatically activate plugins after installation', | |
'updates_label' => 'Enable auto-updates', | |
'updates_description' => 'Enable automatic updates for installed plugins', | |
// Feature flags | |
'allow_activation' => true, | |
'allow_updates' => true, | |
'show_instructions' => true, | |
// Footer configuration | |
'footer_left_text' => 'From <a href="https://snipsnip.pro/s/868" target="_blank">snipsnip.pro/s/868</a>', | |
'footer_right_text' => '<a href="https://github.com/brandonjp/wp-bulk-plugin-uploader" target="_blank">View on GitHub</a>', | |
'show_footer' => true | |
]; | |
/** @var array Deployment options with defaults */ | |
private $options = [ | |
'replace_existing' => false, | |
'activate_plugins' => false, | |
'enable_updates' => false | |
]; | |
/** | |
* Returns single instance of this class | |
* | |
* @param array $config Optional configuration override | |
* @return WP_Bulk_Plugin_Upload | |
*/ | |
public static function get_instance($config = []) { | |
if (self::$instance === null) { | |
self::$instance = new self($config); | |
} | |
return self::$instance; | |
} | |
/** | |
* Constructor - Sets up actions and filters | |
* | |
* @param array $config Configuration override | |
*/ | |
private function __construct($config = []) { | |
// Merge provided config with defaults | |
$this->config = wp_parse_args($config, self::$default_config); | |
add_action('admin_menu', [$this, 'add_admin_menus']); | |
add_action('admin_init', [$this, 'handle_upload']); | |
add_action('admin_notices', [$this, 'display_notices']); | |
add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_styles']); | |
add_action('admin_head', [$this, 'add_admin_buttons']); | |
// Add footer hooks | |
add_action('admin_footer_text', [$this, 'custom_admin_footer_text']); | |
add_filter('update_footer', [$this, 'custom_update_footer'], 11); | |
} | |
/** | |
* Enqueues admin styles | |
*/ | |
public function enqueue_admin_styles($hook) { | |
// Only load on our plugin page | |
if (strpos($hook, $this->config['menu_slug']) === false) { | |
return; | |
} | |
// Add inline CSS for flex classes | |
$custom_css = " | |
[class*='bp-flex-'] { | |
width: 100%; | |
display: flex; | |
flex-wrap: nowrap; | |
align-content: center; | |
justify-content: center; | |
align-items: center; | |
gap: 0.9rem; | |
} | |
[class*='bp-flex-'][class*='-row'] { | |
flex-direction: row; | |
} | |
[class*='bp-flex-'][class*='-stack'] { | |
flex-direction: column; | |
} | |
[class*='bp-flex-'][class*='-wrap'] { | |
flex-wrap: wrap; | |
} | |
[class*='bp-flex-'][class*='-nowrap'] { | |
flex-wrap: nowrap; | |
} | |
[class*='bp-flex-'][class*='-center'] { | |
align-items: center; | |
align-content: center; | |
justify-content: center; | |
} | |
[class*='bp-flex-'][class*='-top'], | |
[class*='bp-flex-'][class*='-left'], | |
[class*='bp-flex-'][class*='-start'] { | |
align-items: flex-start; | |
align-content: flex-start; | |
justify-content: flex-start; | |
} | |
[class*='bp-flex-'][class*='-bottom'], | |
[class*='bp-flex-'][class*='-right'], | |
[class*='bp-flex-'][class*='-end'] { | |
align-items: flex-end; | |
align-content: flex-end; | |
justify-content: flex-end; | |
} | |
"; | |
wp_add_inline_style('wp-admin', $custom_css); | |
} | |
/** | |
* Adds menu items based on configuration | |
*/ | |
public function add_admin_menus() { | |
$page_config = [ | |
'page_title' => $this->config['page_title'], | |
'menu_title' => $this->config['menu_title'], | |
'capability' => $this->config['capability'], | |
'menu_slug' => $this->config['menu_slug'], | |
'callback' => [$this, 'render_admin_page'] | |
]; | |
// Add to Tools menu | |
if (in_array($this->config['menu_parent'], ['tools', 'both'])) { | |
add_management_page(...array_values($page_config)); | |
} | |
// Add to Plugins menu | |
if (in_array($this->config['menu_parent'], ['plugins', 'both'])) { | |
add_plugins_page(...array_values($page_config)); | |
} | |
} | |
/** | |
* Displays admin notices for operation results | |
*/ | |
public function display_notices() { | |
if (empty($this->results)) { | |
return; | |
} | |
$success_count = count(array_filter($this->results, fn($r) => $r['status'] === 'Success')); | |
$skipped_count = count(array_filter($this->results, fn($r) => $r['status'] === 'Skipped')); | |
$failed_count = count(array_filter($this->results, fn($r) => $r['status'] === 'Failed')); | |
$total_count = count($this->results); | |
if ($success_count > 0) { | |
$message = sprintf( | |
'%d of %d plugins processed successfully. Check results table for details.', | |
$success_count, | |
$total_count | |
); | |
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html($message) . '</p></div>'; | |
} | |
if ($skipped_count > 0) { | |
$message = sprintf( | |
'%d plugins were skipped (already installed or invalid). Check results table for details.', | |
$skipped_count | |
); | |
echo '<div class="notice notice-info is-dismissible"><p>' . esc_html($message) . '</p></div>'; | |
} | |
if ($failed_count > 0) { | |
$message = sprintf( | |
'%d plugins failed to process due to errors. Check results table for details.', | |
$failed_count | |
); | |
echo '<div class="notice notice-error is-dismissible"><p>' . esc_html($message) . '</p></div>'; | |
} | |
} | |
/** | |
* Customizes the admin footer text on our plugin page | |
*/ | |
public function custom_admin_footer_text($text) { | |
$screen = get_current_screen(); | |
if ($screen && strpos($screen->id, $this->config['menu_slug']) !== false) { | |
return wp_kses_post($this->config['footer_left_text']); | |
} | |
return $text; | |
} | |
/** | |
* Customizes the version text in the admin footer on our plugin page | |
*/ | |
public function custom_update_footer($text) { | |
$screen = get_current_screen(); | |
if ($screen && strpos($screen->id, $this->config['menu_slug']) !== false) { | |
return wp_kses_post($this->config['footer_right_text']); | |
} | |
return $text; | |
} | |
/** | |
* Renders the admin interface | |
*/ | |
public function render_admin_page() { | |
if (!current_user_can($this->config['capability'])) { | |
wp_die(__('You do not have sufficient permissions to access this page.')); | |
} | |
?> | |
<div class="wrap"> | |
<div> | |
<h1> | |
<?php echo esc_html($this->config['page_title']); ?> | |
<span style="font-size: 13px; color: #646970; font-weight: normal; margin-left: 10px;"> | |
v<?php echo esc_html($this->config['version']); ?> | |
</span> | |
</h1> | |
</div> | |
<?php if (!empty($this->results)): ?> | |
<div class="card" style="margin-bottom: 20px;"> | |
<h2>Results</h2> | |
<table class="widefat"> | |
<thead> | |
<tr> | |
<th>Plugin Name</th> | |
<th>Version</th> | |
<th>Status</th> | |
<th>Details</th> | |
</tr> | |
</thead> | |
<tbody> | |
<?php foreach ($this->results as $result): ?> | |
<tr> | |
<td><?php echo esc_html($result['name']); ?></td> | |
<td><?php echo esc_html($result['version']); ?></td> | |
<td><?php echo esc_html($result['status']); ?></td> | |
<td><?php echo esc_html($result['details']); ?></td> | |
</tr> | |
<?php endforeach; ?> | |
</tbody> | |
</table> | |
</div> | |
<?php endif; ?> | |
<div class="bp-flex-row-top-wrap"> | |
<?php if ($this->config['show_instructions']): ?> | |
<div class="card"> | |
<h2>Instructions</h2> | |
<p style="margin-bottom: 0.5rem;">Upload a ZIP file containing WordPress plugins. The ZIP can contain:</p> | |
<ul style="list-style: square; padding-left: 1.6rem; line-height: 0.8rem; margin: 0.5rem 0 1rem;"> | |
<li>Plugin folders directly</li> | |
<li>Plugin ZIP files</li> | |
<li>A mixture of both folders and ZIP files</li> | |
<li>Optional: All contents in a parent folder</li> | |
</ul> | |
<pre style="background: #f1f1f1; padding: 15px; border-radius: 5px;"> | |
Example structure: | |
plugins.zip | |
├── my-plugin/ # Direct folder | |
├── another-plugin.zip # Compressed plugin | |
└── parent-folder/ # Optional parent folder | |
├── plugin-three/ # containing more plugins | |
└── plugin-four.zip</pre> | |
</div> | |
<?php endif; ?> | |
<div class="card"> | |
<h2>Upload Plugins</h2> | |
<form method="post" enctype="multipart/form-data"> | |
<?php wp_nonce_field('bulk_plugin_upload', 'bulk_plugin_nonce'); ?> | |
<table class="form-table" role="presentation"> | |
<tr> | |
<th scope="row"> | |
<label for="plugins_zip">Select ZIP File</label> | |
</th> | |
<td> | |
<input type="file" | |
name="plugins_zip" | |
id="plugins_zip" | |
accept=".zip" | |
required> | |
<p class="description"> | |
Upload a ZIP file containing your plugins | |
</p> | |
</td> | |
</tr> | |
<tr> | |
<th scope="row">Options</th> | |
<td> | |
<fieldset> | |
<label> | |
<input type="checkbox" | |
name="replace_existing" | |
value="1" | |
<?php checked($this->config['default_replace_existing']); ?>> | |
<?php echo esc_html($this->config['replace_label']); ?> | |
</label> | |
<p class="description"> | |
<?php echo esc_html($this->config['replace_description']); ?> | |
</p> | |
<br><br> | |
<?php if ($this->config['allow_activation']): ?> | |
<label> | |
<input type="checkbox" | |
name="activate_plugins" | |
value="1" | |
<?php checked($this->config['default_activate_plugins']); ?>> | |
<?php echo esc_html($this->config['activate_label']); ?> | |
</label> | |
<p class="description"> | |
<?php echo esc_html($this->config['activate_description']); ?> | |
</p> | |
<br><br> | |
<?php endif; ?> | |
<?php if ($this->config['allow_updates']): ?> | |
<label> | |
<input type="checkbox" | |
name="enable_updates" | |
value="1" | |
<?php checked($this->config['default_enable_updates']); ?>> | |
<?php echo esc_html($this->config['updates_label']); ?> | |
</label> | |
<p class="description"> | |
<?php echo esc_html($this->config['updates_description']); ?> | |
</p> | |
<?php endif; ?> | |
</fieldset> | |
</td> | |
</tr> | |
</table> | |
<?php submit_button($this->config['upload_button_text']); ?> | |
</form> | |
</div> | |
</div> | |
</div> | |
<?php | |
} | |
/** | |
* Handles the plugin upload and installation process | |
*/ | |
public function handle_upload() { | |
if (!isset($_POST['bulk_plugin_nonce']) || | |
!wp_verify_nonce($_POST['bulk_plugin_nonce'], 'bulk_plugin_upload')) { | |
return; | |
} | |
if (!current_user_can($this->config['capability'])) { | |
return; | |
} | |
if (!isset($_FILES['plugins_zip'])) { | |
return; | |
} | |
$zip_file = $_FILES['plugins_zip']; | |
if ($zip_file['error'] !== UPLOAD_ERR_OK) { | |
$this->add_result('Upload Error', '', 'Failed', 'File upload failed'); | |
return; | |
} | |
// Set processing options | |
$this->options = [ | |
'replace_existing' => !empty($_POST['replace_existing']), | |
'activate_plugins' => !empty($_POST['activate_plugins']), | |
'enable_updates' => !empty($_POST['enable_updates']) | |
]; | |
// Process the uploaded ZIP | |
$this->process_zip_file($zip_file['tmp_name']); | |
} | |
/** | |
* Processes the uploaded ZIP file | |
*/ | |
private function process_zip_file($zip_path) { | |
$temp_dir = get_temp_dir() . 'bulk_plugin_upload_' . uniqid(); | |
if (!wp_mkdir_p($temp_dir)) { | |
$this->add_result('System Error', '', 'Failed', 'Could not create temporary directory'); | |
return; | |
} | |
$zip = new ZipArchive(); | |
if ($zip->open($zip_path) !== true) { | |
$this->add_result('ZIP Error', '', 'Failed', 'Could not open ZIP file'); | |
$this->cleanup_dir($temp_dir); | |
return; | |
} | |
$zip->extractTo($temp_dir); | |
$zip->close(); | |
// Look for potential plugin files in root, filtering out system/junk files | |
$items = array_filter( | |
array_diff(scandir($temp_dir), ['.', '..']), | |
function($item) use ($temp_dir) { | |
$path = $temp_dir . '/' . $item; | |
// Keep directories, zip files, and php files that might be plugins | |
$extension = pathinfo($path, PATHINFO_EXTENSION); | |
return (is_dir($path) || | |
(is_file($path) && in_array($extension, ['zip', 'php']))) && | |
!in_array($item, ['.DS_Store', 'index.php']) && | |
strpos($item, '__MACOSX') === false; // Skip macOS metadata | |
} | |
); | |
// Check for common plugin container directory patterns | |
$processed_plugins = false; | |
foreach (['plugins', 'wp-plugins', 'wordpress-plugins'] as $container_name) { | |
$container_path = $temp_dir . '/' . $container_name; | |
if (in_array($container_name, $items) && is_dir($container_path)) { | |
// Found a plugins container directory - process its contents instead | |
$container_items = array_filter( | |
array_diff(scandir($container_path), ['.', '..']), | |
function($item) use ($container_path) { | |
$path = $container_path . '/' . $item; | |
$extension = pathinfo($path, PATHINFO_EXTENSION); | |
return (is_dir($path) || | |
(is_file($path) && in_array($extension, ['zip', 'php']))) && | |
!in_array($item, ['.DS_Store', 'index.php']) && | |
strpos($item, '__MACOSX') === false; | |
} | |
); | |
$this->process_plugin_items($container_path, $container_items); | |
$processed_plugins = true; | |
break; // Only process the first container found | |
} | |
} | |
// If no container directory was found, use original logic | |
if (!$processed_plugins) { | |
// If single folder, look inside it | |
if (count($items) === 1 && is_dir($temp_dir . '/' . reset($items))) { | |
$parent_dir = $temp_dir . '/' . reset($items); | |
// Check if the parent directory itself is a valid plugin | |
if ($this->is_valid_plugin_directory($parent_dir)) { | |
$this->process_plugin_folder($parent_dir); | |
} else { | |
// Look for plugins inside the parent directory | |
$items = array_filter( | |
array_diff(scandir($parent_dir), ['.', '..']), | |
function($item) use ($parent_dir) { | |
$path = $parent_dir . '/' . $item; | |
// Keep directories, zip files, and php files that might be plugins | |
$extension = pathinfo($path, PATHINFO_EXTENSION); | |
return (is_dir($path) || | |
(is_file($path) && in_array($extension, ['zip', 'php']))) && | |
!in_array($item, ['.DS_Store', 'index.php']) && | |
strpos($item, '__MACOSX') === false; | |
} | |
); | |
$this->process_plugin_items($parent_dir, $items); | |
} | |
} else { | |
// Process items in root | |
$this->process_plugin_items($temp_dir, $items); | |
} | |
} | |
$this->cleanup_dir($temp_dir); | |
} | |
/** | |
* Processes a collection of plugin items (folders or ZIPs) | |
*/ | |
private function process_plugin_items($base_path, $items) { | |
foreach ($items as $item) { | |
$path = $base_path . '/' . $item; | |
if (is_dir($path)) { | |
// Check if this directory contains a valid plugin before processing it | |
if ($this->is_valid_plugin_directory($path)) { | |
$this->process_plugin_folder($path); | |
} else { | |
// Skip directories that don't contain valid plugins | |
$this->add_result(basename($path), '', 'Skipped', 'Not a valid plugin directory'); | |
} | |
} elseif (pathinfo($path, PATHINFO_EXTENSION) === 'zip') { | |
$this->process_plugin_zip($path); | |
} elseif (pathinfo($path, PATHINFO_EXTENSION) === 'php') { | |
$this->process_plugin_php_file($path); | |
} | |
} | |
} | |
/** | |
* Checks if a directory contains a valid WordPress plugin | |
*/ | |
private function is_valid_plugin_directory($dir) { | |
if (!is_dir($dir)) { | |
return false; | |
} | |
// Check for a main plugin file with a valid header | |
$main_file = $this->find_main_plugin_file($dir); | |
if ($main_file) { | |
return true; | |
} | |
// If no main plugin file found, check if this is a nested plugin structure | |
// (like a plugin with src/vendor subdirectories) | |
$php_files = glob($dir . '/*.php'); | |
foreach ($php_files as $file) { | |
$content = file_get_contents($file); | |
if (strpos($content, 'Plugin Name:') !== false) { | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* Processes a plugin folder | |
*/ | |
private function process_plugin_folder($folder_path) { | |
// First check if this is a valid plugin directory | |
if (!$this->is_valid_plugin_directory($folder_path)) { | |
$this->add_result(basename($folder_path), '', 'Failed', 'Invalid plugin structure'); | |
return; | |
} | |
$plugin_data = $this->get_plugin_data($folder_path); | |
if (!$plugin_data) { | |
$this->add_result(basename($folder_path), '', 'Failed', 'Could not read plugin data'); | |
return; | |
} | |
$plugin_folder = basename($folder_path); | |
$destination = WP_PLUGIN_DIR . '/' . $plugin_folder; | |
// Check existing plugin | |
$existing_version = ''; | |
$plugin_already_exists = false; | |
if (is_dir($destination)) { | |
$existing_data = $this->get_plugin_data($destination); | |
if ($existing_data) { | |
$existing_version = $existing_data['Version']; | |
$plugin_already_exists = true; | |
if (!$this->options['replace_existing']) { | |
// Check if we should still activate or enable auto-updates | |
if ($this->options['activate_plugins'] || $this->options['enable_updates']) { | |
$main_file = $this->find_main_plugin_file($destination); | |
if ($main_file) { | |
$plugin_file = $plugin_folder . '/' . $main_file; | |
$details = "Already installed (version $existing_version)"; | |
// Handle activation for existing plugin | |
if ($this->options['activate_plugins']) { | |
try { | |
activate_plugin($plugin_file); | |
$details .= ', Activated'; | |
} catch (Exception $e) { | |
$details .= ', Activation failed'; | |
} | |
} | |
// Handle auto-updates for existing plugin | |
if ($this->options['enable_updates']) { | |
$this->enable_plugin_auto_updates([$plugin_file]); | |
$details .= ', Auto-updates enabled'; | |
} | |
$this->add_result( | |
$plugin_data['Name'], | |
$plugin_data['Version'], | |
'Skipped', | |
$details | |
); | |
return; | |
} | |
} | |
$this->add_result( | |
$plugin_data['Name'], | |
$plugin_data['Version'], | |
'Skipped', | |
"Already installed (version $existing_version)" | |
); | |
return; | |
} | |
} | |
} | |
// Copy plugin files | |
if (!$this->copy_plugin($folder_path, $destination)) { | |
$this->add_result( | |
$plugin_data['Name'], | |
$plugin_data['Version'], | |
'Failed', | |
'Could not copy plugin files' | |
); | |
return; | |
} | |
// Build result details | |
$details = $existing_version ? | |
"Replaced version $existing_version" : | |
'Newly installed'; | |
// Handle activation | |
if ($this->options['activate_plugins']) { | |
$main_file = $this->find_main_plugin_file($destination); | |
if ($main_file) { | |
$plugin_file = $plugin_folder . '/' . $main_file; | |
try { | |
activate_plugin($plugin_file); | |
$details .= ', Activated'; | |
} catch (Exception $e) { | |
$details .= ', Activation failed'; | |
} | |
} | |
} | |
// Handle auto-updates | |
if ($this->options['enable_updates']) { | |
$main_file = $this->find_main_plugin_file($destination); | |
if ($main_file) { | |
$plugin_file = $plugin_folder . '/' . $main_file; | |
$this->enable_plugin_auto_updates([$plugin_file]); | |
$details .= ', Auto-updates enabled'; | |
} | |
} | |
$this->add_result( | |
$plugin_data['Name'], | |
$plugin_data['Version'], | |
'Success', | |
$details | |
); | |
} | |
/** | |
* Processes a plugin ZIP file | |
*/ | |
private function process_plugin_zip($zip_path) { | |
$temp_extract_dir = get_temp_dir() . 'plugin_zip_' . uniqid(); | |
if (!wp_mkdir_p($temp_extract_dir)) { | |
$this->add_result(basename($zip_path), '', 'Failed', 'Could not create temporary directory'); | |
return; | |
} | |
$zip = new ZipArchive(); | |
if ($zip->open($zip_path) !== true) { | |
$this->add_result(basename($zip_path), '', 'Failed', 'Could not open plugin ZIP'); | |
$this->cleanup_dir($temp_extract_dir); | |
return; | |
} | |
$zip->extractTo($temp_extract_dir); | |
$zip->close(); | |
// Find the actual plugin directory | |
$items = array_diff(scandir($temp_extract_dir), ['.', '..']); | |
if (count($items) === 1 && is_dir($temp_extract_dir . '/' . reset($items))) { | |
$this->process_plugin_folder($temp_extract_dir . '/' . reset($items)); | |
} else { | |
$this->add_result(basename($zip_path), '', 'Failed', 'Invalid plugin ZIP structure'); | |
} | |
$this->cleanup_dir($temp_extract_dir); | |
} | |
/** | |
* Processes a standalone plugin PHP file | |
*/ | |
private function process_plugin_php_file($php_file_path) { | |
// Read plugin data from the PHP file | |
$plugin_data = get_plugin_data($php_file_path, false, false); | |
if (empty($plugin_data['Name']) || empty($plugin_data['Version'])) { | |
$this->add_result(basename($php_file_path), '', 'Failed', 'Invalid plugin file - missing plugin header'); | |
return; | |
} | |
$plugin_name = sanitize_file_name($plugin_data['Name']); | |
$destination = WP_PLUGIN_DIR . '/' . basename($php_file_path); | |
// Check if plugin already exists | |
$existing_version = ''; | |
if (file_exists($destination)) { | |
$existing_data = get_plugin_data($destination, false, false); | |
if (!empty($existing_data['Version'])) { | |
$existing_version = $existing_data['Version']; | |
if (!$this->options['replace_existing']) { | |
// Check if we should still activate or enable auto-updates | |
if ($this->options['activate_plugins'] || $this->options['enable_updates']) { | |
$plugin_file = basename($php_file_path); | |
$details = "Already installed (version $existing_version)"; | |
// Handle activation for existing plugin | |
if ($this->options['activate_plugins']) { | |
try { | |
activate_plugin($plugin_file); | |
$details .= ', Activated'; | |
} catch (Exception $e) { | |
$details .= ', Activation failed'; | |
} | |
} | |
// Handle auto-updates for existing plugin | |
if ($this->options['enable_updates']) { | |
$this->enable_plugin_auto_updates([$plugin_file]); | |
$details .= ', Auto-updates enabled'; | |
} | |
$this->add_result( | |
$plugin_data['Name'], | |
$plugin_data['Version'], | |
'Skipped', | |
$details | |
); | |
return; | |
} | |
$this->add_result( | |
$plugin_data['Name'], | |
$plugin_data['Version'], | |
'Skipped', | |
"Already installed (version $existing_version)" | |
); | |
return; | |
} | |
} | |
} | |
// Copy the plugin file | |
if (!copy($php_file_path, $destination)) { | |
$this->add_result( | |
$plugin_data['Name'], | |
$plugin_data['Version'], | |
'Failed', | |
'Could not copy plugin file' | |
); | |
return; | |
} | |
// Build result details | |
$details = $existing_version ? | |
"Replaced version $existing_version" : | |
'Newly installed'; | |
// Handle activation | |
if ($this->options['activate_plugins']) { | |
$plugin_file = basename($php_file_path); | |
try { | |
activate_plugin($plugin_file); | |
$details .= ', Activated'; | |
} catch (Exception $e) { | |
$details .= ', Activation failed'; | |
} | |
} | |
// Handle auto-updates | |
if ($this->options['enable_updates']) { | |
$plugin_file = basename($php_file_path); | |
$this->enable_plugin_auto_updates([$plugin_file]); | |
$details .= ', Auto-updates enabled'; | |
} | |
$this->add_result( | |
$plugin_data['Name'], | |
$plugin_data['Version'], | |
'Success', | |
$details | |
); | |
} | |
/** | |
* Gets plugin data from a plugin directory | |
*/ | |
private function get_plugin_data($plugin_dir) { | |
$main_file = $this->find_main_plugin_file($plugin_dir); | |
if (!$main_file) { | |
return false; | |
} | |
return get_plugin_data($plugin_dir . '/' . $main_file, false, false); | |
} | |
/** | |
* Finds the main plugin file in a directory | |
*/ | |
private function find_main_plugin_file($plugin_dir) { | |
if (!is_dir($plugin_dir)) { | |
return false; | |
} | |
$plugin_files = glob($plugin_dir . '/*.php'); | |
foreach ($plugin_files as $file) { | |
$content = file_get_contents($file); | |
if (strpos($content, 'Plugin Name:') !== false) { | |
return basename($file); | |
} | |
} | |
return false; | |
} | |
/** | |
* Copies a plugin directory to its destination | |
*/ | |
private function copy_plugin($source, $destination) { | |
// Request filesystem credentials if needed | |
if (false === ($creds = request_filesystem_credentials(wp_nonce_url('tools.php?page=bulk-plugin-upload', 'bulk_plugin_upload')))) { | |
return false; | |
} | |
if (!WP_Filesystem($creds)) { | |
request_filesystem_credentials(wp_nonce_url('tools.php?page=bulk-plugin-upload', 'bulk_plugin_upload')); | |
return false; | |
} | |
global $wp_filesystem; | |
// Remove existing directory if it exists | |
if ($wp_filesystem->exists($destination)) { | |
$wp_filesystem->delete($destination, true); | |
} | |
// Create the destination directory | |
if (!$wp_filesystem->mkdir($destination)) { | |
return false; | |
} | |
// Copy files | |
return copy_dir($source, $destination); | |
} | |
/** | |
* Cleans up a directory and its contents | |
*/ | |
private function cleanup_dir($dir) { | |
if (!is_dir($dir)) { | |
return; | |
} | |
$files = array_diff(scandir($dir), ['.', '..']); | |
foreach ($files as $file) { | |
$path = $dir . '/' . $file; | |
is_dir($path) ? $this->cleanup_dir($path) : unlink($path); | |
} | |
rmdir($dir); | |
} | |
/** | |
* Checks if a folder contains php files | |
*/ | |
private function directory_contains_php_files($dir) { | |
if (!is_dir($dir)) { | |
return false; | |
} | |
$php_files = glob($dir . '/*.php'); | |
foreach ($php_files as $file) { | |
$content = file_get_contents($file); | |
if (strpos($content, '<?php') !== false) { | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* Adds a result to the results array | |
*/ | |
private function add_result($name, $version, $status, $details) { | |
$this->results[] = [ | |
'name' => $name, | |
'version' => $version, | |
'status' => $status, | |
'details' => $details | |
]; | |
} | |
/** | |
* Helper function to enable auto-updates for plugins | |
*/ | |
private function enable_plugin_auto_updates($plugins) { | |
if (function_exists('wp_is_auto_update_enabled_for_type') && !wp_is_auto_update_enabled_for_type('plugin')) { | |
return false; | |
} | |
if (function_exists('enable_plugins_auto_update')) { | |
return enable_plugins_auto_update($plugins); | |
} | |
foreach ($plugins as $plugin) { | |
$auto_updates = (array) get_site_option('auto_update_plugins', array()); | |
$auto_updates[] = $plugin; | |
$auto_updates = array_unique($auto_updates); | |
update_site_option('auto_update_plugins', $auto_updates); | |
} | |
return true; | |
} | |
/** | |
* Adds buttons to the WordPress admin interface | |
*/ | |
public function add_admin_buttons() { | |
// Only add buttons on specific admin pages | |
$screen = get_current_screen(); | |
if (!$screen) { | |
return; | |
} | |
$screen_id = $screen->id; | |
// Add button to plugins.php page | |
if ($screen_id === 'plugins') { | |
?> | |
<script type="text/javascript"> | |
jQuery(document).ready(function($) { | |
// Add button after the "Add New Plugin" button | |
$('.page-title-action').after('<a href="<?php echo esc_url(admin_url('tools.php?page=' . $this->config['menu_slug'])); ?>" class="page-title-action">Upload Multiple Plugins</a>'); | |
}); | |
</script> | |
<?php | |
} | |
// Add button to plugin-install.php page | |
if ($screen_id === 'plugin-install') { | |
?> | |
<script type="text/javascript"> | |
jQuery(document).ready(function($) { | |
// Add button after the "Upload Plugin" button | |
$('.upload-view-toggle').after('<a href="<?php echo esc_url(admin_url('tools.php?page=' . $this->config['menu_slug'])); ?>" class="page-title-action">Upload Multiple Plugins</a>'); | |
}); | |
</script> | |
<?php | |
} | |
} | |
} | |
// Initialize the plugin with optional configuration | |
WP_Bulk_Plugin_Upload::get_instance([ | |
// Example of overriding default configuration | |
'menu_parent' => 'both', | |
'page_title' => 'Bulk Plugin Upload', | |
'menu_title' => 'Bulk Plugin Upload', | |
// Add any other configuration overrides here | |
]); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment