Files
wpa-superstar-plugin/admin/includes/class-plugin-manager.php
Marcus Quinn 9e1c077080 Refactor plugin structure:
- Rename pro-plugins-config.php to data/pro-plugins.php
- Rename recommended-plugins.php to free-plugins.php
- Rename class-recommended-plugins-manager.php to class-free-plugins-manager.php
- Update all references throughout the codebase
- Add enhanced hover effects to Go Pro buttons
2025-03-24 18:42:24 +00:00

364 lines
17 KiB
PHP

<?php
/**
* WP ALLSTARS Plugin Manager
*
* Core class for handling WordPress plugin data and operations:
* - Plugin data retrieval and caching mechanism
* - AJAX handlers for asynchronous plugin data loading
* - Plugin card UI generation with install/update actions
* - Cache clearing on plugin changes
*
* @package WP_ALLSTARS
* @since 0.2.0
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
/**
* WP_Allstars_Plugin_Manager class
*
* Manages the Free Plugins tab and provides core plugin functionality
* for other plugin-related managers.
*/
class WP_Allstars_Plugin_Manager {
/**
* Initialize the class and register all action hooks
*
* @return void
*/
public static function init() {
// Register AJAX handler for plugin data retrieval
add_action('wp_ajax_wp_allstars_get_plugins', [self::class, 'ajax_get_plugins']);
// Register hooks for automatic cache clearing when plugins change
add_action('upgrader_process_complete', [self::class, 'clear_plugin_cache'], 10, 0);
add_action('activated_plugin', [self::class, 'clear_plugin_cache']);
add_action('deactivated_plugin', [self::class, 'clear_plugin_cache']);
add_action('deleted_plugin', [self::class, 'clear_plugin_cache']);
add_action('update_option_active_plugins', [self::class, 'clear_plugin_cache']);
}
/**
* Get cached plugin data for a specific category
*
* Uses the WordPress transients API to store plugin data
* for improved performance and reduced API calls.
*
* @param string $category The plugin category to retrieve (e.g., 'featured', 'popular')
* @return mixed Array of plugin data if cache exists, false otherwise
*/
public static function get_cached_plugins($category) {
$cache_key = 'wp_allstars_plugins_' . sanitize_key($category);
$cached_data = get_transient($cache_key);
if ($cached_data !== false) {
return $cached_data;
}
return false;
}
/**
* Set cached plugin data for a category
*
* @param string $category The plugin category
* @param mixed $data The data to cache
*/
public static function set_cached_plugins($category, $data) {
$cache_key = 'wp_allstars_plugins_' . $category;
set_transient($cache_key, $data, 12 * HOUR_IN_SECONDS);
}
/**
* AJAX handler for getting plugin data
*/
public static function ajax_get_plugins() {
// Check nonce with the correct action name
if (!check_ajax_referer('wp-allstars-nonce', false, false)) {
wp_send_json_error('Invalid security token sent.');
return;
}
if (!current_user_can('install_plugins')) {
wp_die(-1);
}
$category = isset($_POST['category']) ? sanitize_key($_POST['category']) : 'minimal';
require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
// Get our recommended plugins for this category
$recommended_plugins = wp_allstars_get_free_plugins();
if (!isset($recommended_plugins[$category])) {
wp_send_json_error('Invalid category: ' . $category);
return;
}
// Try to get cached data first
$cached_data = self::get_cached_plugins($category);
if ($cached_data !== false) {
error_log('Using cached data for category: ' . $category);
try {
// Generate plugin cards HTML
$html = self::generate_plugin_cards($cached_data->plugins);
wp_send_json_success($html);
return;
} catch (Exception $e) {
error_log('Error displaying cached plugins: ' . $e->getMessage());
// Fall through to fetch fresh data
}
}
error_log('Fetching fresh data for category: ' . $category);
error_log('Plugins to fetch: ' . implode(', ', $recommended_plugins[$category]));
try {
$plugins = array();
// Only fetch plugins that are in our recommended list for this category
foreach ($recommended_plugins[$category] as $slug) {
try {
error_log('Fetching plugin data for: ' . $slug);
$plugin_data = plugins_api('plugin_information', array(
'slug' => $slug,
'fields' => array(
'short_description' => true,
'sections' => false,
'requires' => true,
'rating' => true,
'ratings' => false,
'downloaded' => true,
'last_updated' => true,
'added' => false,
'tags' => false,
'compatibility' => false,
'homepage' => true,
'versions' => false,
'donate_link' => false,
'reviews' => false,
'banners' => false,
'icons' => true,
'active_installs' => true,
'group' => false,
'contributors' => false,
)
));
if (is_wp_error($plugin_data)) {
error_log('Error fetching plugin data for ' . $slug . ': ' . $plugin_data->get_error_message());
} else {
$plugins[] = $plugin_data;
error_log('Successfully fetched data for: ' . $slug);
}
} catch (Exception $e) {
error_log('Exception fetching plugin data for ' . $slug . ': ' . $e->getMessage());
continue;
}
}
if (empty($plugins)) {
wp_send_json_error('No plugin data could be retrieved for category: ' . $category);
return;
}
error_log('Total plugins fetched: ' . count($plugins));
// Create response object
$res = (object) array(
'info' => array(
'page' => 1,
'pages' => 1,
'results' => count($plugins),
),
'plugins' => $plugins
);
// Cache the results
self::set_cached_plugins($category, $res);
// Generate plugin cards HTML
$html = self::generate_plugin_cards($plugins);
wp_send_json_success($html);
} catch (Exception $e) {
error_log('Failed to fetch plugin data: ' . $e->getMessage());
wp_send_json_error('Failed to fetch plugin data: ' . $e->getMessage());
}
}
/**
* Generate HTML for plugin cards
*
* @param array $plugins Array of plugin data
* @return string HTML for the plugin cards
*/
public static function generate_plugin_cards($plugins) {
if (empty($plugins)) {
return '<div class="notice notice-error"><p>No plugins found.</p></div>';
}
ob_start();
?>
<div class="wp-list-table widefat plugin-install">
<div id="the-list">
<?php foreach ($plugins as $plugin): ?>
<div class="plugin-card plugin-card-<?php echo esc_attr($plugin->slug); ?>">
<div class="plugin-card-top">
<div class="name column-name">
<h3>
<?php echo esc_html($plugin->name); ?>
</h3>
</div>
<div class="action-links">
<ul class="plugin-action-buttons">
<?php
$status = install_plugin_install_status($plugin);
switch ($status['status']) {
case 'install':
echo '<li><a class="button install-now" data-slug="' . esc_attr($plugin->slug) . '" href="' . esc_url($status['url']) . '" aria-label="' . esc_attr(sprintf(__('Install %s now'), $plugin->name)) . '">' . __('Install now') . '</a></li>';
break;
case 'update_available':
echo '<li><a class="button button-primary update-now" data-plugin="' . esc_attr($status['file']) . '" data-slug="' . esc_attr($plugin->slug) . '" href="' . esc_url($status['url']) . '" aria-label="' . esc_attr(sprintf(__('Update %s now'), $plugin->name)) . '">' . __('Update Now') . '</a></li>';
break;
case 'latest_installed':
case 'newer_installed':
if (is_plugin_active($status['file'])) {
echo '<li><button type="button" class="button button-disabled" disabled="disabled">' . __('Active') . '</button></li>';
} else {
echo '<li><a class="button button-primary activate-now" href="' . esc_url(wp_nonce_url('plugins.php?action=activate&amp;plugin=' . $status['file'], 'activate-plugin_' . $status['file'])) . '" aria-label="' . esc_attr(sprintf(__('Activate %s'), $plugin->name)) . '">' . __('Activate') . '</a></li>';
}
break;
}
// Add PRO button if available
self::add_pro_button($plugin);
// Add "More Details" link
echo '<li><a class="thickbox open-plugin-details-modal" href="' . esc_url(admin_url('plugin-install.php?tab=plugin-information&plugin=' . $plugin->slug . '&TB_iframe=true&width=600&height=550')) . '" aria-label="' . esc_attr(sprintf(__('More information about %s'), $plugin->name)) . '">' . __('More Details') . '</a></li>';
?>
</ul>
</div>
<?php if (!empty($plugin->icons) && !empty($plugin->icons['1x'])): ?>
<div class="plugin-icon">
<img src="<?php echo esc_url($plugin->icons['1x']); ?>" alt="">
</div>
<?php endif; ?>
<div class="desc column-description">
<p><?php echo esc_html($plugin->short_description); ?></p>
<p class="authors">
<cite><?php printf(__('By %s'), $plugin->author); ?></cite>
</p>
</div>
</div>
<div class="plugin-card-bottom">
<div class="vers column-rating">
<?php wp_star_rating(array('rating' => $plugin->rating, 'type' => 'percent', 'number' => $plugin->num_ratings)); ?>
<span class="num-ratings" aria-hidden="true">(<?php echo number_format_i18n($plugin->num_ratings); ?>)</span>
</div>
<div class="column-updated">
<strong><?php _e('Last Updated:'); ?></strong>
<?php printf(__('%s ago'), human_time_diff(strtotime($plugin->last_updated))); ?>
</div>
<div class="column-downloaded">
<?php
if ($plugin->active_installs >= 1000000) {
$active_installs_millions = floor($plugin->active_installs / 1000000);
$active_installs_text = sprintf(
_n('%s+ Million Active Installations', '%s+ Million Active Installations', $active_installs_millions),
number_format_i18n($active_installs_millions)
);
} elseif (0 == $plugin->active_installs) {
$active_installs_text = _x('Less Than 10 Active Installations', 'Active plugin installations');
} else {
$active_installs_text = sprintf(
_n('%s+ Active Installation', '%s+ Active Installations', $plugin->active_installs),
number_format_i18n($plugin->active_installs)
);
}
/* translators: %s: number of active installations */
echo esc_html($active_installs_text);
?>
</div>
<div class="column-compatibility">
<?php
$version = get_bloginfo('version');
if (!empty($plugin->tested) && version_compare($version, $plugin->tested, '>')) {
echo '<span class="compatibility-untested">' . __('Untested with your version of WordPress') . '</span>';
} elseif (!empty($plugin->requires) && version_compare($version, $plugin->requires, '<')) {
echo '<span class="compatibility-incompatible">' . __('Incompatible with your version of WordPress') . '</span>';
} else {
echo '<span class="compatibility-compatible">' . __('Compatible with your version of WordPress') . '</span>';
}
?>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php
return ob_get_clean();
}
/**
* Add PRO button to plugin cards if a pro version exists
*
* @param object $plugin The plugin object
*/
public static function add_pro_button($plugin) {
$pro_plugins = wp_allstars_get_pro_plugins();
if (isset($pro_plugins[$plugin->slug])) {
$pro_plugin = $pro_plugins[$plugin->slug];
$pro_url = self::get_pro_plugin_url($pro_plugin);
if (!empty($pro_url)) {
echo '<li><a href="' . esc_url($pro_url) . '" target="_blank" class="button button-primary" style="background-color: #27ae60; border-color: #219653;">' . esc_html__('Go Pro', 'wp-allstars') . '</a></li>';
}
}
}
/**
* Get the URL for a pro plugin from its config
*
* @param array $pro_plugin The pro plugin configuration array
* @return string The URL for the pro plugin
*/
public static function get_pro_plugin_url($pro_plugin) {
// First check if there's a button_group defined
if (isset($pro_plugin['button_group']) && is_array($pro_plugin['button_group'])) {
foreach ($pro_plugin['button_group'] as $button) {
// Return the URL for the primary button if available
if (isset($button['primary']) && $button['primary'] && !empty($button['url'])) {
return $button['url'];
}
}
// If no primary button found, return the first button URL
if (!empty($pro_plugin['button_group'][0]['url'])) {
return $pro_plugin['button_group'][0]['url'];
}
}
// Fall back to the main URL if available
if (!empty($pro_plugin['url'])) {
return $pro_plugin['url'];
}
return '';
}
/**
* Clear plugin cache when plugins are updated, activated, or deactivated
*/
public static function clear_plugin_cache() {
$recommended_plugins = wp_allstars_get_free_plugins();
foreach (array_keys($recommended_plugins) as $category) {
delete_transient('wp_allstars_plugins_' . $category);
}
}
}