Revert "Refactor(Admin): Implement Settings API & AJAX save for Settings Manager"
This reverts commit f65d648a82.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user