Revert "Refactor(Admin): Implement Settings API & AJAX save for Settings Manager"

This reverts commit f65d648a82.
This commit is contained in:
2025-04-19 13:15:29 +01:00
parent f65d648a82
commit a3bf7fc78f
26 changed files with 1594 additions and 2194 deletions
+169
View File
@@ -0,0 +1,169 @@
<?php
/**
* WP ALLSTARS Admin Colors Feature
*
* Handles setting the admin color scheme based on user preferences
*
* @package WP_ALLSTARS
* @since 0.2.3.1
*/
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
/**
* Admin Colors Handler Class
*/
class WP_Allstars_Admin_Colors {
/**
* Option name for the admin color scheme setting
*
* @var string
*/
private $option_name = 'wp_allstars_admin_color_scheme';
/**
* Modern color scheme key
*
* @var string
*/
private $modern_scheme = 'modern';
/**
* Default color scheme key
*
* @var string
*/
private $default_scheme = 'fresh';
/**
* Initialize the class and set up hooks
*/
public function __construct() {
// Set up hooks
add_action('admin_init', array($this, 'set_admin_color_scheme'));
add_action('wp_ajax_wp_allstars_update_color_scheme', array($this, 'handle_color_scheme_update'));
// Add script to handle the toggle
add_action('admin_enqueue_scripts', array($this, 'enqueue_color_scripts'));
}
/**
* Enqueue scripts and styles for the color scheme toggle
*/
public function enqueue_color_scripts() {
// Only enqueue on our plugin pages
$screen = get_current_screen();
if (!isset($screen->id) || strpos($screen->id, 'wp-allstars') === false) {
return;
}
wp_enqueue_script(
'wp-allstars-color-toggle',
plugin_dir_url(dirname(__FILE__)) . 'admin/js/wp-allstars-admin-colors.js',
array('jquery'),
WP_ALLSTARS_VERSION,
true
);
wp_localize_script('wp-allstars-color-toggle', 'wpAllstarsColors', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('wp_allstars_color_nonce'),
'option_name' => $this->option_name,
));
}
/**
* Set the admin color scheme based on user preference
*/
public function set_admin_color_scheme() {
// Only apply for administrators
if (!current_user_can('manage_options')) {
return;
}
// Get current user
$user_id = get_current_user_id();
if (!$user_id) {
return;
}
// Check if our option is enabled
$enable_modern = get_option($this->option_name, false);
// Set the appropriate color scheme
if ($enable_modern) {
// Use modern scheme if available, otherwise use default
$this->set_user_color_scheme($user_id, $this->modern_scheme);
}
}
/**
* Set a user's color scheme
*
* @param int $user_id The user ID
* @param string $scheme The color scheme to set
*/
private function set_user_color_scheme($user_id, $scheme) {
// Check if the scheme exists
global $_wp_admin_css_colors;
// If the scheme doesn't exist, use the default
if (!isset($_wp_admin_css_colors[$scheme])) {
$scheme = $this->default_scheme;
}
// Update the user's color scheme
update_user_meta($user_id, 'admin_color', $scheme);
}
/**
* Handle AJAX request to update color scheme
*/
public function handle_color_scheme_update() {
// Verify nonce
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'wp_allstars_color_nonce')) {
wp_send_json_error('Invalid nonce');
}
// Verify user can manage options
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
// Get the new value
$enabled = isset($_POST['enabled']) ? (bool) $_POST['enabled'] : false;
// Update the option
update_option($this->option_name, $enabled);
// Get current user
$user_id = get_current_user_id();
// Set the color scheme
if ($enabled) {
$this->set_user_color_scheme($user_id, $this->modern_scheme);
$message = 'Modern admin colors enabled';
} else {
$this->set_user_color_scheme($user_id, $this->default_scheme);
$message = 'Default admin colors restored';
}
// Send success response
wp_send_json_success(array(
'message' => $message,
'enabled' => $enabled,
));
}
/**
* Check if modern color scheme is enabled
*
* @return bool Whether modern color scheme is enabled
*/
public function is_modern_color_scheme_enabled() {
return (bool) get_option($this->option_name, false);
}
}
+136
View File
@@ -0,0 +1,136 @@
<?php
/**
* Auto Upload Images functionality
*
* @package WP_ALLSTARS
* @since 0.2.0
*/
if (!defined('ABSPATH')) {
exit;
}
class WP_Allstars_Auto_Upload {
/**
* Initialize the class
*/
public function __construct() {
add_filter('content_save_pre', array($this, 'process_content'));
add_action('wp_allstars_image_upload_error', array($this, 'log_error'), 10, 2);
}
/**
* Process content for external images
*
* @param string $content The post content
* @return string Modified content with local image URLs
*/
public function process_content($content) {
// Check if auto upload is enabled
$options = get_option('wp_allstars_workflow_options', array('auto_upload_images' => false));
if (!$options['auto_upload_images']) {
return $content;
}
// Regular expression to find image URLs
$pattern = '/<img[^>]+src=[\'"]([^\'"]+)[\'"][^>]*>/i';
return preg_replace_callback($pattern, array($this, 'process_image_url'), $content);
}
/**
* Process individual image URL
*
* @param array $matches Regex matches
* @return string Updated img tag
*/
private function process_image_url($matches) {
if (empty($matches[1])) {
return $matches[0];
}
$url = $matches[1];
// Skip if already a local URL
if ($this->is_local_url($url)) {
return $matches[0];
}
try {
$local_url = $this->upload_image($url);
if ($local_url) {
return str_replace($url, $local_url, $matches[0]);
}
} catch (Exception $e) {
// Trigger error action for logging
do_action('wp_allstars_image_upload_error', esc_url($url), $e->getMessage());
}
return $matches[0];
}
/**
* Check if URL is local
*
* @param string $url URL to check
* @return boolean
*/
private function is_local_url($url) {
$site_url = parse_url(get_site_url(), PHP_URL_HOST);
$image_host = parse_url($url, PHP_URL_HOST);
return $site_url === $image_host;
}
/**
* Upload external image to media library
*
* @param string $url External image URL
* @return string|false Local URL on success, false on failure
* @throws Exception If download or upload fails
*/
private function upload_image($url) {
$file_array = array(
'name' => sanitize_file_name(basename($url))
);
// Download file to temp location
$file_array['tmp_name'] = download_url($url);
if (is_wp_error($file_array['tmp_name'])) {
throw new Exception('Failed to download image: ' . $file_array['tmp_name']->get_error_message());
}
// Check file type for security
$wp_filetype = wp_check_filetype_and_ext($file_array['tmp_name'], $file_array['name']);
if (!$wp_filetype['type']) {
unlink($file_array['tmp_name']);
throw new Exception('Invalid file type');
}
// Upload the file to media library
$attachment_id = media_handle_sideload($file_array, 0);
if (is_wp_error($attachment_id)) {
unlink($file_array['tmp_name']);
throw new Exception('Failed to upload image: ' . $attachment_id->get_error_message());
}
return wp_get_attachment_url($attachment_id);
}
/**
* Log errors to WordPress debug log
*
* @param string $url URL that failed
* @param string $error Error message
*/
public function log_error($url, $error) {
if (WP_DEBUG) {
error_log(sprintf(
'[WP ALLSTARS] Auto Upload Images Error - URL: %s, Error: %s',
esc_url_raw($url),
sanitize_text_field($error)
));
}
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php
/**
* WP ALLSTARS Sync Guard
*
* Prevents plugin loading during sync operations to avoid fatal errors.
*
* @package WP_ALLSTARS
* @since 0.2.3.1
*/
if (!defined('ABSPATH')) {
exit;
}
/**
* Class responsible for detecting sync operations and preventing plugin loading
*/
class WP_Allstars_Sync_Guard {
/**
* Flag file name for sync operations
*/
const SYNC_FLAG_FILE = '.syncing';
/**
* Check if sync is in progress
*
* @return bool Whether sync is in progress
*/
public static function is_sync_in_progress() {
$flag_file = plugin_dir_path(dirname(__FILE__)) . self::SYNC_FLAG_FILE;
return file_exists($flag_file);
}
/**
* Handle sync mode by showing admin notice and preventing plugin loading
*
* @return bool True if sync is in progress, false otherwise
*/
public static function handle_sync_mode() {
if (self::is_sync_in_progress()) {
// Add admin notice
add_action('admin_notices', array(__CLASS__, 'display_sync_notice'));
// Return true to indicate plugin should not continue loading
return true;
}
return false;
}
/**
* Display sync in progress notice
*/
public static function display_sync_notice() {
echo '<div class="notice notice-warning is-dismissible">';
echo '<p><strong>WP Allstars:</strong> Plugin files are currently syncing. The plugin functionality is temporarily disabled to prevent errors. Please try again in a moment.</p>';
echo '</div>';
}
}
@@ -0,0 +1,330 @@
<?php
/**
* WP Allstars UI Enhancements
*
* Responsible for enhancing the WordPress admin interface with improved UI components
* like cards, panels, buttons, and responsive design elements.
*
* @package WP_ALLSTARS
* @version v0.2.3.3
*/
if (!defined('WPINC')) {
exit;
}
class WP_Allstars_UI_Enhancements {
/**
* Constructor
* Initialize hooks and settings
*/
public function __construct() {
// Register scripts and styles
add_action('admin_enqueue_scripts', array($this, 'enqueue_assets'));
// Add body class for enhanced UI
add_filter('admin_body_class', array($this, 'add_body_class'));
// Initialize UI components
$this->init_components();
// Ensure toggle functionality works
add_action('admin_footer', array($this, 'ensure_toggle_functionality'), 99);
}
/**
* Enqueue CSS and JavaScript assets
*/
public function enqueue_assets($hook) {
// Only load on WP Allstars pages
if (strpos($hook, 'wp-allstars') === false) {
return;
}
// Already registered in main plugin file, but ensure they're enqueued
wp_enqueue_style('wp-allstars-admin');
wp_enqueue_script('wp-allstars-admin');
// Add UI enhancements script
wp_enqueue_script(
'wp-allstars-ui-enhancements',
plugin_dir_url(dirname(__FILE__)) . 'admin/js/wp-allstars-ui-enhancements.js',
array('jquery', 'wp-allstars-admin'),
WP_ALLSTARS_VERSION,
true
);
// Add enhanced UI styles
wp_enqueue_style(
'wp-allstars-ui-enhancements',
plugin_dir_url(dirname(__FILE__)) . 'admin/css/wp-allstars-ui-enhancements.css',
array('wp-allstars-admin'),
WP_ALLSTARS_VERSION
);
// Localize script with settings
wp_localize_script('wp-allstars-ui-enhancements', 'wpAllstarsUI', array(
'ajaxurl' => admin_url('ajax.php'),
'nonce' => wp_create_nonce('wp_allstars_ui_nonce'),
));
}
/**
* Add body class for enhanced UI
*/
public function add_body_class($classes) {
if (isset($_GET['page']) && strpos($_GET['page'], 'wp-allstars') !== false) {
$classes .= ' wp-allstars-enhanced-ui';
}
return $classes;
}
/**
* Initialize UI components
*/
private function init_components() {
// Add accordion functionality
add_action('admin_footer', array($this, 'render_accordion_template'));
// Add card component
add_action('admin_footer', array($this, 'render_card_template'));
// Add notification system
add_action('admin_footer', array($this, 'render_notification_template'));
}
/**
* Ensure toggle switch functionality
* This adds JS to reinitialize toggle switch handlers after our enhanced UI is applied
*/
public function ensure_toggle_functionality() {
// Only on WP Allstars pages
if (!isset($_GET['page']) || strpos($_GET['page'], 'wp-allstars') === false) {
return;
}
// Get the nonce value
$nonce = wp_create_nonce('wp-allstars-nonce');
?>
<script type="text/javascript">
jQuery(document).ready(function($) {
// Make sure the wpAllstars object is available
if (typeof wpAllstars === 'undefined') {
window.wpAllstars = {
ajaxurl: '<?php echo esc_url(admin_url('admin-ajax.php')); ?>',
nonce: '<?php echo esc_js($nonce); ?>'
};
}
// Remove any existing handlers first to prevent duplicates
$('.wp-toggle-switch input[type="checkbox"]').off('change');
$('.wp-allstars-toggle-header').off('click');
// Re-bind toggle switch handlers
$('.wp-toggle-switch input[type="checkbox"]').on('change', function() {
console.log('Toggle switch changed:', this.id);
var $this = $(this);
var option = $this.attr('id');
var value = $this.is(':checked') ? 1 : 0;
// Don't handle the admin color scheme toggle here - it has its own handler
if (option === 'wp_allstars_admin_color_scheme') {
return;
}
// Show update notification
var $notification = $this.closest('.wp-setting-left').find('.wp-setting-notification');
if ($notification.length === 0) {
$notification = $('<span class="wp-setting-notification">Saving...</span>');
$this.closest('.wp-setting-left').append($notification);
} else {
$notification.text('Saving...').removeClass('error').show();
}
// Save the option via AJAX
$.ajax({
url: wpAllstars.ajaxurl,
type: 'POST',
data: {
action: 'wp_allstars_update_option',
nonce: wpAllstars.nonce,
option: option,
value: value
},
success: function(response) {
if (response.success) {
$notification.text('Saved!');
setTimeout(function() {
$notification.fadeOut(300);
}, 2000);
} else {
$notification.text('Error').addClass('error');
console.error('Error saving option:', response.data);
}
},
error: function(xhr, status, error) {
$notification.text('Error').addClass('error');
console.error('AJAX error:', error);
}
});
});
// Re-bind expandable panels
$('.wp-allstars-toggle-header').on('click', function() {
var $this = $(this);
var $settings = $this.next('.wp-allstars-toggle-settings');
var isExpanded = $this.attr('aria-expanded') === 'true';
// Toggle aria-expanded attribute
$this.attr('aria-expanded', !isExpanded);
// Toggle settings visibility
$settings.slideToggle(200);
});
// Special handling for admin color scheme toggle if exists
if (typeof wpAllstarsColors !== 'undefined') {
var $colorToggle = $('#wp_allstars_admin_color_scheme');
if ($colorToggle.length) {
$colorToggle.off('change').on('change', function() {
var isModern = $(this).is(':checked');
// Show saving notification
var $notification = $colorToggle.closest('.wp-setting-left').find('.wp-setting-notification');
if ($notification.length === 0) {
$notification = $('<span class="wp-setting-notification">Saving...</span>');
$colorToggle.closest('.wp-setting-left').append($notification);
} else {
$notification.text('Saving...').removeClass('error').show();
}
// Save the option via AJAX
$.ajax({
url: wpAllstarsColors.ajaxurl,
type: 'POST',
data: {
action: 'wp_allstars_update_color_scheme',
nonce: wpAllstarsColors.nonce,
is_modern: isModern ? 1 : 0
},
success: function(response) {
if (response.success) {
if (isModern) {
$('body').addClass('wp-allstars-modern-admin');
} else {
$('body').removeClass('wp-allstars-modern-admin');
}
$notification.text('Saved!');
setTimeout(function() {
$notification.fadeOut(300);
}, 2000);
} else {
$notification.text('Error').addClass('error');
console.error('Error updating color scheme:', response.data);
// Revert toggle
$colorToggle.prop('checked', !isModern);
}
},
error: function(xhr, status, error) {
$notification.text('Error').addClass('error');
console.error('AJAX error:', error);
// Revert toggle
$colorToggle.prop('checked', !isModern);
}
});
});
}
}
});
</script>
<?php
}
/**
* Render accordion template
*/
public function render_accordion_template() {
// Only on WP Allstars pages
if (!isset($_GET['page']) || strpos($_GET['page'], 'wp-allstars') === false) {
return;
}
?>
<script type="text/html" id="tmpl-wp-allstars-accordion">
<div class="wp-allstars-accordion" role="tablist">
<div class="wp-allstars-accordion-header" role="tab" id="accordion-header-{{data.id}}" aria-expanded="false">
<div class="wp-allstars-accordion-title">{{data.title}}</div>
<div class="wp-allstars-accordion-icon"></div>
</div>
<div class="wp-allstars-accordion-content" role="tabpanel" aria-labelledby="accordion-header-{{data.id}}">
<div class="wp-allstars-accordion-inner">
{{{data.content}}}
</div>
</div>
</div>
</script>
<?php
}
/**
* Render card template
*/
public function render_card_template() {
// Only on WP Allstars pages
if (!isset($_GET['page']) || strpos($_GET['page'], 'wp-allstars') === false) {
return;
}
?>
<script type="text/html" id="tmpl-wp-allstars-card">
<div class="wp-allstars-card">
<# if (data.header) { #>
<div class="wp-allstars-card-header">
<# if (data.icon) { #>
<div class="wp-allstars-card-icon">{{{data.icon}}}</div>
<# } #>
<div class="wp-allstars-card-title">{{data.header}}</div>
</div>
<# } #>
<div class="wp-allstars-card-content">
{{{data.content}}}
</div>
<# if (data.footer) { #>
<div class="wp-allstars-card-footer">
{{{data.footer}}}
</div>
<# } #>
</div>
</script>
<?php
}
/**
* Render notification template
*/
public function render_notification_template() {
// Only on WP Allstars pages
if (!isset($_GET['page']) || strpos($_GET['page'], 'wp-allstars') === false) {
return;
}
?>
<script type="text/html" id="tmpl-wp-allstars-notification">
<div class="wp-allstars-notification wp-allstars-notification-{{data.type}}">
<div class="wp-allstars-notification-icon"></div>
<div class="wp-allstars-notification-content">
<# if (data.title) { #>
<div class="wp-allstars-notification-title">{{data.title}}</div>
<# } #>
<div class="wp-allstars-notification-message">{{data.message}}</div>
</div>
<div class="wp-allstars-notification-dismiss"></div>
</div>
</script>
<?php
}
}
-237
View File
@@ -1,237 +0,0 @@
<?php
/**
* WPALLSTARS Admin Colors Feature
*
* Manages the admin color scheme override, allowing users to toggle between
* the default WordPress scheme and a custom 'modern' scheme provided by the plugin.
*
* @package WPALLSTARS
* @subpackage Core
* @since 1.0.0
*/
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly.
}
/**
* Class WPALLSTARS_Admin_Colors
*
* Handles the registration, application, and AJAX updates for the admin color scheme setting.
*/
class WPALLSTARS_Admin_Colors {
/**
* Option key stored in wp_options table for the toggle setting.
* Uses 'wpallstars_options' array with a specific key for better organization.
*
* @var string
*/
private $options_key = 'wpallstars_options';
/**
* Specific key within the options array for the color scheme setting.
*
* @var string
*/
private $color_scheme_option_key = 'admin_color_scheme_enabled'; // Matches setting registration
/**
* The slug/key for the custom 'modern' color scheme.
* Assumes this scheme is registered elsewhere (e.g., via wp_admin_css_color).
*
* @var string
*/
private $modern_scheme_slug = 'modern'; // Ensure this matches registration
/**
* The slug/key for the default WordPress color scheme ('fresh').
*
* @var string
*/
private $default_scheme_slug = 'fresh';
/**
* Nonce action name for security checks in AJAX handler.
*
* @var string
*/
private $ajax_nonce_action = 'wpallstars_update_color_scheme_nonce'; // More specific nonce
/**
* Initialize the class and set up WordPress hooks.
*/
public function __construct() {
// Hook into admin initialization to potentially apply the scheme.
add_action('admin_init', array($this, 'apply_user_admin_color_scheme'), 10); // Priority 10 is standard
// Hook into AJAX action for updating the color scheme setting.
add_action('wp_ajax_wpallstars_update_admin_color_scheme', array($this, 'handle_ajax_color_scheme_update'));
// Hook to enqueue scripts specifically for the color scheme toggle functionality.
// Note: This assumes the toggle appears on WP Allstars admin pages.
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_color_scripts'));
}
/**
* Enqueue JavaScript and localize data for the color scheme toggle.
*
* Only enqueues scripts on admin pages related to WP Allstars.
*
* @param string $hook_suffix The hook suffix of the current admin page.
*/
public function enqueue_admin_color_scripts($hook_suffix) {
// Use a more robust check for WP Allstars pages (using the registered page hook)
$wpallstars_pages = [
'toplevel_page_wpallstars-settings', // Example for top-level page
'wpallstars_page_wpallstars-settings-network', // Example for network admin
// Add other WP Allstars page slugs as needed
];
// If using Admin_Manager, get the registered page hook slug.
// For now, keep the simpler check, but ideally use the actual hook.
$screen = get_current_screen();
if (!$screen || strpos($screen->id, 'wpallstars') === false) {
return; // Exit if not a WP Allstars page.
}
// Enqueue the specific JS file for color handling.
wp_enqueue_script(
'wpallstars-admin-colors-script', // More specific handle
WPALLSTARS_URL . 'admin/js/wpallstars-admin-colors.js', // Use constant for URL
array('jquery'),
WPALLSTARS_VERSION, // Use constant for version
true // Load in footer
);
// Localize data needed by the script.
wp_localize_script(
'wpallstars-admin-colors-script', // Must match the script handle
'wpallstarsAdminColorsData', // JavaScript object name
array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce($this->ajax_nonce_action),
'ajax_action' => 'wpallstars_update_admin_color_scheme', // The action hook name
'l10n' => [ // Localization strings
'saving' => __('Saving...', WPALLSTARS_TEXT_DOMAIN),
'saved' => __('Saved', WPALLSTARS_TEXT_DOMAIN),
'error' => __('Error', WPALLSTARS_TEXT_DOMAIN),
]
)
);
}
/**
* Apply the selected admin color scheme for the current user upon admin initialization.
*
* Checks if the user has the capability and if the setting is enabled.
*/
public function apply_user_admin_color_scheme() {
// Ensure the current user has the capability to change schemes (usually 'manage_options' or similar).
if (!current_user_can('manage_options')) { // Adjust capability if needed
return;
}
$user_id = get_current_user_id();
if (!$user_id) {
return; // Should not happen in admin_init, but good practice.
}
// Check if the modern scheme override is enabled in settings.
$scheme_to_apply = $this->is_modern_color_scheme_enabled()
? $this->modern_scheme_slug
: $this->default_scheme_slug;
// Get the user's currently saved color scheme preference.
$current_user_scheme = get_user_meta($user_id, 'admin_color', true);
// Only update the user's meta if the desired scheme differs from their current one.
// This prevents unnecessary database writes on every admin page load.
if ($current_user_scheme !== $scheme_to_apply) {
$this->update_user_color_scheme_preference($user_id, $scheme_to_apply);
}
}
/**
* Update a user's color scheme preference in their user meta.
*
* @param int $user_id The ID of the user to update.
* @param string $scheme_slug The slug of the color scheme to set (e.g., 'modern', 'fresh').
*/
private function update_user_color_scheme_preference($user_id, $scheme_slug) {
// WordPress handles validation internally, but ensure the scheme exists if crucial.
// global $_wp_admin_css_colors;
// if (!isset($_wp_admin_css_colors[$scheme_slug])) {
// $scheme_slug = $this->default_scheme_slug; // Fallback to default
// }
// Update the 'admin_color' user meta field.
update_user_meta($user_id, 'admin_color', $scheme_slug);
}
/**
* Handle the AJAX request to enable/disable the modern color scheme override.
*
* Verifies nonce, checks user capabilities, updates the option,
* updates the current user's scheme immediately, and sends a JSON response.
*/
public function handle_ajax_color_scheme_update() {
// 1. Verify Nonce for security.
check_ajax_referer($this->ajax_nonce_action, 'nonce'); // Dies on failure
// 2. Check User Capabilities.
if (!current_user_can('manage_options')) { // Ensure user can change this setting
wp_send_json_error(array('message' => __('Insufficient permissions.', WPALLSTARS_TEXT_DOMAIN)), 403); // 403 Forbidden
}
// 3. Sanitize and Validate Input.
// Expecting 'enabled' to be 'true' or 'false' (as strings from JS).
$is_enabled_input = isset($_POST['enabled']) ? sanitize_text_field($_POST['enabled']) : 'false';
$is_enabled = ($is_enabled_input === 'true'); // Convert string 'true' to boolean true
// 4. Update the Option in the Database.
$options = get_option($this->options_key, []);
$options[$this->color_scheme_option_key] = $is_enabled ? 1 : 0; // Store as 1 or 0
$update_success = update_option($this->options_key, $options);
// 5. Update Current User's Scheme Immediately for instant feedback.
$user_id = get_current_user_id();
$scheme_to_set = $is_enabled ? $this->modern_scheme_slug : $this->default_scheme_slug;
$this->update_user_color_scheme_preference($user_id, $scheme_to_set);
// 6. Send JSON Response.
if ($update_success) {
wp_send_json_success(array(
'message' => $is_enabled
? __('Modern admin color scheme enabled.', WPALLSTARS_TEXT_DOMAIN)
: __('Default admin color scheme restored.', WPALLSTARS_TEXT_DOMAIN),
'new_state' => $is_enabled, // Send back the new state
));
} else {
// Option update might have failed, or the value was unchanged.
// Check if the value was actually unchanged.
$current_db_options = get_option($this->options_key, []);
$current_db_value = isset($current_db_options[$this->color_scheme_option_key]) ? (bool)$current_db_options[$this->color_scheme_option_key] : false;
if ($current_db_value === $is_enabled) {
wp_send_json_success(array(
'message' => __('Setting unchanged.', WPALLSTARS_TEXT_DOMAIN),
'new_state' => $is_enabled,
'unchanged' => true
));
} else {
wp_send_json_error(array('message' => __('Failed to save setting.', WPALLSTARS_TEXT_DOMAIN)), 500); // 500 Internal Server Error
}
}
}
/**
* Check if the modern color scheme override is enabled in the plugin settings.
*
* @return bool True if enabled, false otherwise.
*/
public function is_modern_color_scheme_enabled() {
$options = get_option($this->options_key, []);
// Check if the specific key exists and is set to 1 (or true). Default to false if not set.
return isset($options[$this->color_scheme_option_key]) && $options[$this->color_scheme_option_key] == 1;
}
}
-220
View File
@@ -1,220 +0,0 @@
<?php
/**
* Auto Upload Images functionality for WP Allstars Plugin.
*
* Automatically downloads external images found in post content upon saving
* and uploads them to the WordPress media library, replacing the external URLs.
*
* @package WPALLSTARS
* @subpackage Core
* @since 0.2.0
*/
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly.
}
/**
* Class WPALLSTARS_Auto_Upload
*
* Manages the automatic uploading of external images embedded in post content.
*/
class WPALLSTARS_Auto_Upload {
/**
* Initialize the hooks for the Auto Upload feature.
*/
public function __construct() {
// Hook into content saving process. Priority 10 is standard.
add_filter('content_save_pre', array($this, 'process_content_for_images'), 10, 1);
// Hook for logging errors, allowing other parts of the plugin or themes to hook in too.
add_action('wpallstars_image_upload_error', array($this, 'log_upload_error'), 10, 2);
}
/**
* Process post content before saving to find and upload external images.
*
* Checks if the feature is enabled via settings before processing.
*
* @param string $content The raw post content being saved.
* @return string Modified content with external image URLs replaced by local ones,
* or the original content if the feature is disabled or no images are found.
*/
public function process_content_for_images($content) {
// Retrieve workflow options, providing a default for 'auto_upload_images'
$workflow_options = get_option('wpallstars_workflow_options', array('auto_upload_images' => 0)); // Default to disabled (0)
// Check if the 'auto_upload_images' setting is enabled (expecting '1' if enabled)
if (empty($workflow_options['auto_upload_images']) || $workflow_options['auto_upload_images'] != '1') {
return $content; // Feature disabled, return original content.
}
// Regular expression to find <img> tags and capture their src attribute.
// This pattern is case-insensitive (i) and handles single or double quotes.
$pattern = '/<img[^>]+src=[\'"]([^\'"]+)[\'"][^>]*>/i';
// Use preg_replace_callback to process each found image URL individually.
$modified_content = preg_replace_callback($pattern, array($this, 'replace_image_url_callback'), $content);
// Return the modified content, or the original if preg_replace_callback encountered an error.
return (null === $modified_content) ? $content : $modified_content;
}
/**
* Callback function for preg_replace_callback. Processes a single image match.
*
* @param array $matches An array of matches from preg_replace_callback.
* $matches[0] is the full <img> tag.
* $matches[1] is the URL from the src attribute.
* @return string The original <img> tag if the URL is local or upload fails,
* or the modified <img> tag with the new local URL.
*/
private function replace_image_url_callback($matches) {
// Ensure the URL capture group exists.
if (empty($matches[1])) {
return $matches[0]; // Return original tag if no URL found.
}
$external_url = trim($matches[1]);
// Validate the URL format (basic check).
if (filter_var($external_url, FILTER_VALIDATE_URL) === false) {
// Optionally log invalid URL format if needed
// $this->log_upload_error($external_url, 'Invalid URL format detected');
return $matches[0]; // Invalid URL, skip.
}
// Skip if the URL is already pointing to the local site.
if ($this->is_local_image_url($external_url)) {
return $matches[0]; // Already local, no need to process.
}
try {
// Attempt to upload the image.
$local_url = $this->sideload_image($external_url);
// If upload is successful, replace the URL in the img tag.
if ($local_url) {
// Use str_replace for simple replacement within the matched tag.
// This is generally safe as we're replacing the exact matched URL string.
return str_replace($external_url, esc_url($local_url), $matches[0]);
}
} catch (Exception $e) {
// Trigger a WordPress action to log the error.
// Pass the original external URL and the exception message.
do_action('wpallstars_image_upload_error', esc_url($external_url), $e->getMessage());
}
// Return the original <img> tag if the upload failed or an exception occurred.
return $matches[0];
}
/**
* Check if a given image URL belongs to the local WordPress site.
*
* Compares the host of the image URL with the host of the site URL.
*
* @param string $url The image URL to check.
* @return bool True if the URL is local, false otherwise.
*/
private function is_local_image_url($url) {
// Get the host part of the site URL (e.g., '[www.example.com](www.example.com)').
$site_host = parse_url(get_site_url(), PHP_URL_HOST);
// Get the host part of the image URL.
$image_host = parse_url($url, PHP_URL_HOST);
// Return true if the hosts match, false otherwise.
// Use strtolower for case-insensitive comparison.
return !empty($site_host) && !empty($image_host) && strtolower($site_host) === strtolower($image_host);
}
/**
* Download an external image and upload it to the WordPress media library.
*
* Uses WordPress core functions download_url() and media_handle_sideload().
*
* @param string $external_url The URL of the external image to download.
* @return string|false The local URL of the uploaded image on success, false on failure.
* @throws Exception If downloading or uploading fails, or if the file type is invalid.
*/
private function sideload_image($external_url) {
// Ensure required WordPress core files for media handling are loaded.
require_once ABSPATH . 'wp-admin/includes/media.php';
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/image.php'; // For potential image processing
$file_array = array();
// Sanitize filename derived from the URL path.
$file_array['name'] = sanitize_file_name(basename(parse_url($external_url, PHP_URL_PATH)));
// Download the file to a temporary location.
// Set timeout to prevent long waits for unresponsive URLs.
$timeout_seconds = 15;
$temp_file_path = download_url($external_url, $timeout_seconds);
// Check for download errors.
if (is_wp_error($temp_file_path)) {
// Clean up temporary file if it exists, even on error
if (file_exists($temp_file_path)) {
@unlink($temp_file_path);
}
throw new Exception(sprintf('Failed to download image: %s', $temp_file_path->get_error_message()));
}
// Check file type for security. Associate with the temporary file.
$file_array['tmp_name'] = $temp_file_path;
$wp_filetype = wp_check_filetype_and_ext($file_array['tmp_name'], $file_array['name']);
// Check if the file type is allowed by WordPress.
if (empty($wp_filetype['ext']) || empty($wp_filetype['type']) || !in_array(strtolower($wp_filetype['ext']), get_allowed_mime_types()) && !in_array($wp_filetype['type'], get_allowed_mime_types())) {
unlink($file_array['tmp_name']); // Clean up the temporary file.
throw new Exception(sprintf('Invalid or disallowed file type detected: %s', $wp_filetype['type'] ?: 'Unknown'));
}
// Upload the file from the temporary location to the media library.
// The post ID '0' indicates the attachment is not associated with any specific post.
$attachment_id = media_handle_sideload($file_array, 0);
// Check for errors during the sideloading process.
if (is_wp_error($attachment_id)) {
unlink($file_array['tmp_name']); // Clean up the temporary file.
throw new Exception(sprintf('Failed to upload image: %s', $attachment_id->get_error_message()));
}
// Get the URL of the newly uploaded attachment.
$local_url = wp_get_attachment_url($attachment_id);
// Check if a valid URL was returned.
if (!$local_url) {
// Attachment likely created, but URL failed. Try to clean up attachment? Difficult.
// Best practice is often to leave the attachment and log the URL retrieval error.
unlink($file_array['tmp_name']); // Still clean up temp file
throw new Exception(sprintf('Failed to get attachment URL after upload (Attachment ID: %d)', $attachment_id));
}
// Return the local URL on success.
return $local_url;
}
/**
* Log image upload errors using WordPress's error logging mechanism.
*
* Only logs if WP_DEBUG is enabled.
*
* @param string $failed_url The external URL that failed to upload.
* @param string $error_message The specific error message.
*/
public function log_upload_error($failed_url, $error_message) {
// Check if WordPress debugging is enabled.
if (defined('WP_DEBUG') && WP_DEBUG === true) {
error_log(sprintf(
'[WPALLSTARS Auto Upload] Error: Failed to process image "%s". Reason: %s',
esc_url_raw($failed_url), // Use raw URL for logging
sanitize_text_field($error_message)
));
}
}
}
-291
View File
@@ -1,291 +0,0 @@
<?php
/**
* WPALLSTARS Sync Guard
*
* Prevents accidental overwrites during content synchronization or updates.
* Adds checks before saving posts or terms, potentially comparing modification dates
* or using a locking mechanism to avoid data loss when multiple sources might update content.
*
* @package WPALLSTARS
* @subpackage Core
* @since 1.0.0 // Adjust version as needed
*/
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly.
}
/**
* Class WPALLSTARS_Sync_Guard
*
* Implements mechanisms to protect content from concurrent edits or overwrites
* during synchronization processes.
*/
class WPALLSTARS_Sync_Guard {
/**
* Option key for Sync Guard settings within the main options array.
*
* @var string
*/
private $options_key = 'wpallstars_options';
/**
* Key for the enable/disable setting within the options array.
*
* @var string
*/
private $setting_key_enabled = 'sync_guard_enabled';
/**
* Key for the sync guard mode (e.g., 'timestamp', 'lock').
*
* @var string
*/
private $setting_key_mode = 'sync_guard_mode';
/**
* Meta key to store the last sync timestamp on a post/term.
*
* @var string
*/
private $meta_key_last_sync = '_wpallstars_last_sync_timestamp';
/**
* Meta key to store a lock indicator (e.g., user ID and timestamp).
*
* @var string
*/
private $meta_key_lock = '_wpallstars_sync_lock';
/**
* Lock timeout in seconds.
*
* @var int
*/
private $lock_timeout = 300; // 5 minutes
/**
* Constructor. Sets up hooks if the feature is enabled.
*/
public function __construct() {
if ($this->is_enabled()) {
// Hook into pre-save actions for posts and potentially terms
// Priority 10 is default, allowing other plugins to modify data first.
add_filter('wp_insert_post_data', array($this, 'check_post_sync_conflict'), 10, 3); // Use 3 args for postarr, update, WP_Error
// Hook into post-save action to update meta or release locks.
add_action('save_post', array($this, 'update_post_sync_meta'), 20, 2); // Run later to capture final state
// --- Term Hooks (Example - Not included in this edit for brevity) ---
// --- Optional AJAX Lock Release (Not included in this edit for brevity) ---
}
}
/**
* Check if Sync Guard is enabled in settings.
*
* @return bool True if enabled, false otherwise.
*/
private function is_enabled() {
$options = get_option($this->options_key, []);
// Ensure the key exists and is explicitly set to '1' or true.
return !empty($options[$this->setting_key_enabled]) && $options[$this->setting_key_enabled] == 1;
}
/**
* Get the configured sync guard mode from settings.
*
* @return string The mode ('timestamp', 'lock', or default 'timestamp').
*/
private function get_mode() {
$options = get_option($this->options_key, []);
$mode = isset($options[$this->setting_key_mode]) ? sanitize_key($options[$this->setting_key_mode]) : 'timestamp';
// Ensure mode is one of the allowed values
return in_array($mode, ['timestamp', 'lock'], true) ? $mode : 'timestamp'; // Default to timestamp
}
/**
* Check for potential sync conflicts before saving post data.
* Runs on the 'wp_insert_post_data' filter.
*
* @param array $data An array of slashed post data.
* @param array $postarr An array of sanitized, but otherwise unmodified post data.
* @param bool|WP_Error $update Whether this is an update. WP_Error if validation failed.
* @return array|WP_Error The original $data or a WP_Error if a conflict is detected and blocking is configured.
*/
public function check_post_sync_conflict($data, $postarr, $update) {
// --- Basic Checks ---
// 1. Only act on updates (existing posts).
// 2. Ignore if validation already failed ($update is WP_Error).
// 3. Ensure we have a Post ID.
// 4. Check if the post type is relevant.
if (!$update || is_wp_error($update) || empty($postarr['ID']) || !$this->is_relevant_post_type($postarr['post_type'])) {
return $data; // Pass through if not applicable
}
$post_id = absint($postarr['ID']);
$mode = $this->get_mode();
$current_user_id = get_current_user_id(); // Can be 0 for system processes
// --- Timestamp Mode Logic ---
if ($mode === 'timestamp') {
$last_sync_time_str = get_post_meta($post_id, $this->meta_key_last_sync, true);
$post_modified_gmt_str = isset($data['post_modified_gmt']) ? $data['post_modified_gmt'] : get_post_field('post_modified_gmt', $post_id);
// Convert to timestamps for comparison
$last_sync_ts = !empty($last_sync_time_str) ? strtotime($last_sync_time_str) : 0;
$post_modified_ts = !empty($post_modified_gmt_str) ? strtotime($post_modified_gmt_str) : 0;
// Check if a sync occurred *after* the last modification recorded in the database.
// This implies an external update happened since the content being edited was loaded.
if ($last_sync_ts > 0 && $post_modified_ts > 0 && $last_sync_ts > $post_modified_ts) {
// Conflict detected: External sync is newer than the post's last known modification time.
// Option 1: Block the save (disruptive) - Uncomment if needed
/*
return new WP_Error('sync_conflict_timestamp',
__('Warning: This content appears to have been updated by an external sync process since you started editing. Saving now would overwrite those changes. Please reload the content.', WPALLSTARS_TEXT_DOMAIN)
);
*/
// Option 2: Allow save but log it (less disruptive)
$log_message = sprintf(
'[WPALLSTARS Sync Guard] Post ID %d: Potential timestamp conflict detected. Save allowed. Last Sync: %s, DB Post Modified: %s',
$post_id,
esc_html($last_sync_time_str),
esc_html($post_modified_gmt_str)
);
error_log($log_message);
}
}
// --- Lock Mode Logic ---
elseif ($mode === 'lock') {
$lock_data = get_post_meta($post_id, $this->meta_key_lock, true);
if (!empty($lock_data) && is_array($lock_data)) {
$lock_time = isset($lock_data['time']) ? (int) $lock_data['time'] : 0;
$lock_user = isset($lock_data['user']) ? (int) $lock_data['user'] : -1; // -1 for unknown/system lock owner
// Check if lock is expired
if ((time() - $lock_time) > $this->lock_timeout) {
$this->release_post_lock($post_id, 'timeout_expired');
// Lock released due to timeout, proceed to acquire new lock below.
}
// Check if lock is held by someone else and not expired
elseif ($lock_user !== $current_user_id) {
$locked_by_user = ($lock_user > 0) ? get_userdata($lock_user) : null;
$locked_by_name = $locked_by_user ? $locked_by_user->display_name : __('another process', WPALLSTARS_TEXT_DOMAIN);
$time_ago = human_time_diff($lock_time);
$error_message = sprintf(
// translators: %1$s: User display name or 'another process'. %2$s: Time duration (e.g., '5 minutes ago').
__('Sync Conflict: This content is currently locked for editing by %1$s (since %2$s ago). Please try again later or ask them to finish.', WPALLSTARS_TEXT_DOMAIN),
esc_html($locked_by_name),
esc_html($time_ago)
);
// Block the save by returning a WP_Error
return new WP_Error('sync_lock_conflict', $error_message);
}
// Else: Lock is held by the current user, allow save to proceed.
}
// If we reached here (no conflict or lock expired), attempt to acquire or update the lock.
// This ensures the lock is held throughout the save process.
$this->acquire_post_lock($post_id, $current_user_id);
}
// No conflict or conflict handled (e.g., logging), allow data to pass through.
return $data;
}
/**
* Update sync-related meta data after a post is saved.
* Runs on the 'save_post' action.
*
* @param int $post_id Post ID.
* @param WP_Post $post Post object.
*/
public function update_post_sync_meta($post_id, $post) {
// Prevent infinite loops and ensure post type is relevant
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
if (wp_is_post_revision($post_id)) return;
if (!$this->is_relevant_post_type($post->post_type)) return;
$mode = $this->get_mode();
// Update timestamp after successful save (if in timestamp mode)
if ($mode === 'timestamp') {
// Update our custom meta field to reflect the latest save time.
// This uses the post_modified_gmt from the saved post object.
update_post_meta($post_id, $this->meta_key_last_sync, $post->post_modified_gmt);
}
// Release lock after successful save (if in lock mode)
elseif ($mode === 'lock') {
// Only release if the lock is held by the current user who initiated the save.
$lock_data = get_post_meta($post_id, $this->meta_key_lock, true);
$current_user_id = get_current_user_id();
if (!empty($lock_data) && is_array($lock_data) && isset($lock_data['user']) && $lock_data['user'] === $current_user_id) {
$this->release_post_lock($post_id, 'save_completed_by_user');
}
// Locks held by other users or expired locks should have been handled earlier or will time out.
}
}
/**
* Acquire or update a lock on a post for the current user/process.
*
* @param int $post_id Post ID.
* @param int $user_id User ID acquiring the lock (0 for system/unknown).
* @return bool True on success, false on failure.
*/
private function acquire_post_lock($post_id, $user_id) {
$lock_data = array(
'user' => (int) $user_id,
'time' => time(), // Current server time
);
// Use update_post_meta - it handles both adding and updating the meta key.
$result = update_post_meta($post_id, $this->meta_key_lock, $lock_data);
if (!$result) {
error_log("[WPALLSTARS Sync Guard] Failed to acquire/update lock for Post ID {$post_id} by User ID {$user_id}.");
}
return (bool) $result;
}
/**
* Release a lock on a post.
*
* @param int $post_id Post ID.
* @param string $reason Optional reason for logging purposes.
* @return bool True if the meta key was deleted, false otherwise.
*/
private function release_post_lock($post_id, $reason = '') {
$log_message = sprintf(
'[WPALLSTARS Sync Guard] Releasing lock for Post ID %d. Reason: %s',
absint($post_id),
sanitize_text_field($reason)
);
error_log($log_message);
// Deleting the meta key removes the lock.
return delete_post_meta($post_id, $this->meta_key_lock);
}
/**
* Check if the post type is relevant for sync guarding.
* Post types can be configured via the 'wpallstars_sync_guard_post_types' filter.
*
* @param string $post_type The post type slug.
* @return bool True if the post type should be guarded, false otherwise.
*/
private function is_relevant_post_type($post_type) {
// Default relevant post types
$default_types = array('post', 'page');
// Allow themes/plugins to filter the list of guarded post types
$relevant_types = apply_filters('wpallstars_sync_guard_post_types', $default_types);
// Ensure it's an array before checking
return is_array($relevant_types) && in_array($post_type, $relevant_types, true);
}
// --- Methods for Term Guarding (Example Structure - requires implementation) ---
// Term guarding logic would go here, mirroring the post logic but using
// term meta (get/update/delete_term_meta) and term-related hooks.
}
@@ -1,176 +0,0 @@
<?php
/**
* WPALLSTARS UI Enhancements
*
* Responsible for enhancing the WordPress admin interface with improved UI components
* like cards, panels, buttons, and responsive design elements.
*
* @package WPALLSTARS
* @version v0.2.3.3
*/
if (!defined('WPINC')) {
exit;
}
class WPALLSTARS_UI_Enhancements {
/**
* Constructor
* Initialize hooks and settings
*/
public function __construct() {
// Register scripts and styles
add_action('admin_enqueue_scripts', array($this, 'enqueue_assets'));
// Add body class for enhanced UI
add_filter('admin_body_class', array($this, 'add_body_class'));
// Initialize UI components
$this->init_components();
}
/**
* Enqueue CSS and JavaScript assets
*/
public function enqueue_assets($hook) {
// Only load on WPALLSTARS pages
if (strpos($hook, 'wpallstars') === false) {
return;
}
// Already registered in main plugin file, but ensure they're enqueued
wp_enqueue_style('wpallstars-admin');
wp_enqueue_script('wpallstars-admin');
// Add UI enhancements script
wp_enqueue_script(
'wpallstars-ui-enhancements',
WPALLSTARS_URL . 'admin/js/wpallstars-ui-enhancements.js',
array('jquery', 'wpallstars-admin'),
WPALLSTARS_VERSION,
true
);
// Add enhanced UI styles
wp_enqueue_style(
'wpallstars-ui-enhancements',
WPALLSTARS_URL . 'admin/css/wpallstars-ui-enhancements.css',
array('wpallstars-admin'),
WPALLSTARS_VERSION
);
// Localize script with settings
wp_localize_script('wpallstars-ui-enhancements', 'wpallstarsUI', array(
'ajaxurl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('wpallstars_ui_nonce'),
));
}
/**
* Add body class for enhanced UI
*/
public function add_body_class($classes) {
if (isset($_GET['page']) && strpos($_GET['page'], 'wpallstars') !== false) {
$classes .= ' wpallstars-ui-enabled';
}
return $classes;
}
/**
* Initialize UI components
*/
private function init_components() {
// Add accordion functionality
add_action('admin_footer', array($this, 'render_accordion_template'));
// Add card component
add_action('admin_footer', array($this, 'render_card_template'));
// Add notification system
add_action('admin_footer', array($this, 'render_notification_template'));
}
/**
* Render accordion template
*/
public function render_accordion_template() {
// Only on WPALLSTARS pages
if (!isset($_GET['page']) || strpos($_GET['page'], 'wpallstars') === false) {
return;
}
?>
<script type="text/html" id="tmpl-wpallstars-accordion">
<div class="wpallstars-accordion" role="tablist">
<div class="wpallstars-accordion-header" role="tab" id="accordion-header-{{data.id}}" aria-expanded="false">
<div class="wpallstars-accordion-title">{{data.title}}</div>
<div class="wpallstars-accordion-icon"></div>
</div>
<div class="wpallstars-accordion-content" role="tabpanel" aria-labelledby="accordion-header-{{data.id}}">
<div class="wpallstars-accordion-inner">
{{{data.content}}}
</div>
</div>
</div>
</script>
<?php
}
/**
* Render card template
*/
public function render_card_template() {
// Only on WPALLSTARS pages
if (!isset($_GET['page']) || strpos($_GET['page'], 'wpallstars') === false) {
return;
}
?>
<script type="text/html" id="tmpl-wpallstars-card">
<div class="wpallstars-card">
<# if (data.header) { #>
<div class="wpallstars-card-header">
<# if (data.icon) { #>
<div class="wpallstars-card-icon">{{{data.icon}}}</div>
<# } #>
<div class="wpallstars-card-title">{{data.header}}</div>
</div>
<# } #>
<div class="wpallstars-card-content">
{{{data.content}}}
</div>
<# if (data.footer) { #>
<div class="wpallstars-card-footer">
{{{data.footer}}}
</div>
<# } #>
</div>
</script>
<?php
}
/**
* Render notification template
*/
public function render_notification_template() {
// Only on WPALLSTARS pages
if (!isset($_GET['page']) || strpos($_GET['page'], 'wpallstars') === false) {
return;
}
?>
<script type="text/html" id="tmpl-wpallstars-notification">
<div class="wpallstars-notification wpallstars-notification-{{data.type}}">
<div class="wpallstars-notification-icon"></div>
<div class="wpallstars-notification-content">
<# if (data.title) { #>
<div class="wpallstars-notification-title">{{data.title}}</div>
<# } #>
<div class="wpallstars-notification-message">{{data.message}}</div>
</div>
<div class="wpallstars-notification-dismiss"></div>
</div>
</script>
<?php
}
}