Enhance admin UI and UX for WPTag plugin

Improves the admin interface with refined CSS for better layout, accessibility, and responsiveness. Adds robust JS logic for disabling tracking scripts, change tracking, validation, preview, import/export, reset, tooltips, and modal handling. These changes provide a more user-friendly experience, prevent accidental data loss, and ensure tracking scripts do not run in the admin area.
This commit is contained in:
feibisi 2025-07-26 05:47:24 +08:00
parent 3c4c1f8e2e
commit 30e040134d
10 changed files with 3290 additions and 685 deletions

View file

@ -28,6 +28,8 @@ class Admin {
}
private function init_hooks() {
add_filter('wptag_should_output_codes', '__return_false', 999);
add_action('admin_menu', array($this, 'add_admin_menu'));
add_action('admin_init', array($this, 'admin_init'));
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets'));
@ -120,7 +122,10 @@ class Admin {
'reset_success' => 'Settings reset successfully',
'confirm_reset' => 'Are you sure you want to reset all settings? This cannot be undone.',
'confirm_import' => 'This will overwrite your current settings. Continue?',
'loading' => 'Loading...'
'loading' => 'Loading...',
'fill_required' => 'Please fill in required fields for enabled services.',
'advanced_settings' => 'Advanced Settings',
'hide_advanced' => 'Hide Advanced Settings'
)
));
}
@ -130,7 +135,10 @@ class Admin {
wp_die(__('You do not have sufficient permissions to access this page.'));
}
$this->handle_form_submission();
add_filter('wptag_should_output_codes', '__return_false', 999);
remove_action('wp_head', array($this->frontend ?? null, 'output_head_codes'), 1);
remove_action('wp_body_open', array($this->frontend ?? null, 'output_body_codes'), 1);
remove_action('wp_footer', array($this->frontend ?? null, 'output_footer_codes'), 1);
$active_tab = isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : 'analytics';
$categories = $this->get_categories_with_services();
@ -143,13 +151,25 @@ class Admin {
<div class="wptag-header">
<div class="wptag-header-info">
<p>Manage your tracking codes and analytics services with ease.</p>
<p>Manage your tracking codes and analytics services with ease. Enable services in the Services Management tab, then configure them in their respective category tabs.</p>
<?php if (defined('WP_DEBUG') && WP_DEBUG): ?>
<p><small><strong>Debug Mode:</strong> Check your website's source code for WPTag debug comments if codes are not appearing.</small></p>
<?php endif; ?>
</div>
<div class="wptag-header-actions">
<?php if ($this->current_user_can_manage_codes()): ?>
<button type="button" class="button" id="wptag-export-btn">Export Settings</button>
<button type="button" class="button" id="wptag-import-btn">Import Settings</button>
<button type="button" class="button button-secondary" id="wptag-reset-btn">Reset All</button>
<button type="button" class="button" id="wptag-export-btn">
<span class="dashicons dashicons-download"></span>
Export Settings
</button>
<button type="button" class="button" id="wptag-import-btn">
<span class="dashicons dashicons-upload"></span>
Import Settings
</button>
<button type="button" class="button button-secondary" id="wptag-reset-btn">
<span class="dashicons dashicons-admin-generic"></span>
Reset All
</button>
<?php endif; ?>
</div>
</div>
@ -157,14 +177,18 @@ class Admin {
<nav class="nav-tab-wrapper">
<?php foreach ($categories as $category_key => $category_data): ?>
<a href="?page=wptag-settings&tab=<?php echo esc_attr($category_key); ?>"
class="nav-tab <?php echo $active_tab === $category_key ? 'nav-tab-active' : ''; ?>">
class="nav-tab <?php echo $active_tab === $category_key ? 'nav-tab-active' : ''; ?>"
data-tab="<?php echo esc_attr($category_key); ?>">
<span class="dashicons dashicons-chart-area"></span>
<?php echo esc_html(ucfirst($category_key)); ?>
<span class="count">(<?php echo count($category_data['services']); ?>)</span>
</a>
<?php endforeach; ?>
<?php if ($this->current_user_can_manage_services()): ?>
<a href="?page=wptag-settings&tab=services"
class="nav-tab <?php echo $active_tab === 'services' ? 'nav-tab-active' : ''; ?>">
class="nav-tab <?php echo $active_tab === 'services' ? 'nav-tab-active' : ''; ?>"
data-tab="services">
<span class="dashicons dashicons-admin-settings"></span>
Services Management
</a>
<?php endif; ?>
@ -181,7 +205,8 @@ class Admin {
<input type="file" id="wptag-import-file" accept=".json" style="display: none;">
<div id="wptag-preview-modal" style="display: none;">
<div id="wptag-preview-modal" class="wptag-modal" style="display: none;">
<div class="wptag-modal-backdrop"></div>
<div class="wptag-modal-content">
<div class="wptag-modal-header">
<h3>Code Preview</h3>
@ -214,14 +239,17 @@ class Admin {
?>
<div class="wptag-services-management">
<div class="wptag-services-header">
<p>Enable or disable tracking services. Only enabled services will appear in the category tabs.</p>
<div class="wptag-services-info">
<p>Enable or disable tracking services. Only enabled services will appear in the category tabs.</p>
<p><small><strong>Custom Code Support:</strong> When using custom code, JavaScript will be preserved without filtering - perfect for gtag(), fbq(), and other tracking functions.</small></p>
</div>
<div class="wptag-services-actions">
<button type="button" class="button" id="wptag-enable-all">Enable All</button>
<button type="button" class="button" id="wptag-disable-all">Disable All</button>
</div>
</div>
<form method="post" action="">
<form method="post" action="" id="wptag-services-form">
<?php wp_nonce_field('wptag_save_services', 'wptag_services_nonce'); ?>
<?php foreach ($categories as $category_name => $category_services): ?>
@ -229,7 +257,8 @@ class Admin {
<h2><?php echo esc_html(ucfirst($category_name)); ?></h2>
<div class="wptag-services-grid">
<?php foreach ($category_services as $service_key => $service_config): ?>
<div class="wptag-service-item service-<?php echo esc_attr($service_key); ?>">
<div class="wptag-service-item <?php echo in_array($service_key, $enabled_services) ? 'enabled' : 'disabled'; ?>"
data-service="<?php echo esc_attr($service_key); ?>">
<div class="wptag-service-info">
<div class="wptag-service-icon">
<span class="dashicons <?php echo esc_attr($service_config['icon']); ?>"></span>
@ -283,13 +312,17 @@ class Admin {
private function display_category_tab($active_tab, $category_data) {
if (empty($category_data['services'])) {
echo '<div class="wptag-no-services">';
echo '<p>No services enabled for this category. <a href="?page=wptag-settings&tab=services">Enable some services</a> to get started.</p>';
echo '<div class="wptag-no-services-icon">';
echo '<span class="dashicons dashicons-admin-settings"></span>';
echo '</div>';
echo '<h3>No Services Available</h3>';
echo '<p>No services are enabled for this category. <a href="?page=wptag-settings&tab=services">Enable some services</a> to get started.</p>';
echo '</div>';
return;
}
?>
<form method="post" action="" class="wptag-settings-form">
<form method="post" action="" class="wptag-settings-form" id="wptag-settings-form">
<?php wp_nonce_field('wptag_save_settings', 'wptag_nonce'); ?>
<div class="wptag-services-grid">
@ -299,7 +332,7 @@ class Admin {
</div>
<div class="wptag-form-actions">
<?php submit_button('Save Settings', 'primary', 'save_settings', false); ?>
<?php submit_button('Save Settings', 'primary', 'save_settings', false, array('id' => 'wptag-save-btn')); ?>
</div>
</form>
<?php
@ -309,8 +342,10 @@ class Admin {
$service_settings = $this->config->get_service_settings($service_key);
$field_key = $service_config['field'];
$can_edit = $this->current_user_can_manage_codes();
$is_enabled = !empty($service_settings['enabled']);
?>
<div class="wptag-service-card" data-service="<?php echo esc_attr($service_key); ?>">
<div class="wptag-service-card <?php echo $is_enabled ? 'enabled' : 'disabled'; ?>"
data-service="<?php echo esc_attr($service_key); ?>">
<div class="wptag-service-header">
<div class="wptag-service-icon">
<span class="dashicons <?php echo esc_attr($service_config['icon']); ?>"></span>
@ -339,7 +374,7 @@ class Admin {
value="1"
<?php checked($service_settings['use_template']); ?>
<?php disabled(!$can_edit); ?>>
Template
<span>Template</span>
</label>
<label>
<input type="radio"
@ -347,7 +382,7 @@ class Admin {
value="0"
<?php checked($service_settings['use_template'], false); ?>
<?php disabled(!$can_edit); ?>>
Custom
<span>Custom</span>
</label>
</div>
</div>
@ -356,6 +391,7 @@ class Admin {
<div class="wptag-form-row">
<label class="wptag-form-label" for="<?php echo esc_attr($service_key . '_' . $field_key); ?>">
<?php echo esc_html(ucfirst(str_replace('_', ' ', $field_key))); ?>
<span class="required">*</span>
</label>
<div class="wptag-input-group">
<input type="text"
@ -364,17 +400,24 @@ class Admin {
value="<?php echo esc_attr($service_settings[$field_key]); ?>"
placeholder="<?php echo esc_attr($service_config['placeholder']); ?>"
class="wptag-input"
<?php disabled(!$can_edit); ?>>
<?php disabled(!$can_edit); ?>
data-required="true">
<?php if ($can_edit): ?>
<button type="button" class="button wptag-validate-btn" data-service="<?php echo esc_attr($service_key); ?>">
<span class="dashicons dashicons-yes"></span>
Validate
</button>
<button type="button" class="button wptag-preview-btn" data-service="<?php echo esc_attr($service_key); ?>">
<span class="dashicons dashicons-visibility"></span>
Preview
</button>
<?php endif; ?>
</div>
<div class="wptag-validation-result"></div>
<div class="wptag-field-help">
<small><?php echo esc_html($this->get_template_help_text($service_key, $service_config)); ?></small>
<small><a href="<?php echo esc_url($this->get_service_docs_url($service_key)); ?>" target="_blank" rel="noopener">View <?php echo esc_html($service_config['name']); ?> documentation</a></small>
</div>
</div>
</div>
@ -382,6 +425,7 @@ class Admin {
<div class="wptag-form-row">
<label class="wptag-form-label" for="<?php echo esc_attr($service_key . '_custom_code'); ?>">
Custom Code
<span class="required">*</span>
</label>
<div class="wptag-code-editor-wrapper">
<textarea id="<?php echo esc_attr($service_key . '_custom_code'); ?>"
@ -389,62 +433,72 @@ class Admin {
rows="12"
placeholder="Paste your complete tracking code here..."
class="wptag-code-editor"
<?php disabled(!$can_edit); ?>><?php echo esc_textarea($service_settings['custom_code']); ?></textarea>
<div class="wptag-code-editor-toolbar">
<button type="button" class="button wptag-format-code" title="Format Code">
<span class="dashicons dashicons-editor-code"></span>
</button>
<button type="button" class="button wptag-clear-code" title="Clear Code">
<span class="dashicons dashicons-trash"></span>
</button>
</div>
<?php disabled(!$can_edit); ?>
data-required="true"><?php echo esc_textarea($service_settings['custom_code']); ?></textarea>
<?php if ($can_edit): ?>
<div class="wptag-code-editor-toolbar">
<button type="button" class="button wptag-format-code" title="Format Code">
<span class="dashicons dashicons-editor-code"></span>
</button>
<button type="button" class="button wptag-clear-code" title="Clear Code">
<span class="dashicons dashicons-trash"></span>
</button>
</div>
<?php endif; ?>
</div>
<?php if ($can_edit): ?>
<div class="wptag-input-group">
<button type="button" class="button wptag-validate-btn" data-service="<?php echo esc_attr($service_key); ?>">
<span class="dashicons dashicons-yes"></span>
Validate
</button>
</div>
<?php endif; ?>
<div class="wptag-validation-result"></div>
<div class="wptag-field-help">
<small><?php echo esc_html($this->get_service_help_text($service_key, $service_config)); ?></small>
<small><a href="<?php echo esc_url($this->get_service_docs_url($service_key)); ?>" target="_blank" rel="noopener">View <?php echo esc_html($service_config['name']); ?> documentation</a></small>
</div>
</div>
</div>
<div class="wptag-advanced-settings" style="display: none;">
<div class="wptag-form-row">
<label class="wptag-form-label">Position</label>
<select name="wptag_settings[<?php echo esc_attr($service_key); ?>][position]"
class="wptag-select" <?php disabled(!$can_edit); ?>>
<option value="head" <?php selected($service_settings['position'], 'head'); ?>>Head</option>
<option value="body" <?php selected($service_settings['position'], 'body'); ?>>Body</option>
<option value="footer" <?php selected($service_settings['position'], 'footer'); ?>>Footer</option>
</select>
</div>
<div class="wptag-advanced-settings">
<div class="wptag-advanced-grid">
<div class="wptag-form-row">
<label class="wptag-form-label">Position</label>
<select name="wptag_settings[<?php echo esc_attr($service_key); ?>][position]"
class="wptag-select" <?php disabled(!$can_edit); ?>>
<option value="head" <?php selected($service_settings['position'], 'head'); ?>>Head</option>
<option value="body" <?php selected($service_settings['position'], 'body'); ?>>Body</option>
<option value="footer" <?php selected($service_settings['position'], 'footer'); ?>>Footer</option>
</select>
</div>
<div class="wptag-form-row">
<label class="wptag-form-label">Priority</label>
<input type="number"
name="wptag_settings[<?php echo esc_attr($service_key); ?>][priority]"
value="<?php echo esc_attr($service_settings['priority']); ?>"
min="1"
max="100"
class="wptag-input wptag-input-small"
<?php disabled(!$can_edit); ?>>
</div>
<div class="wptag-form-row">
<label class="wptag-form-label">Priority</label>
<input type="number"
name="wptag_settings[<?php echo esc_attr($service_key); ?>][priority]"
value="<?php echo esc_attr($service_settings['priority']); ?>"
min="1"
max="100"
class="wptag-input wptag-input-small"
<?php disabled(!$can_edit); ?>>
</div>
<div class="wptag-form-row">
<label class="wptag-form-label">Device</label>
<select name="wptag_settings[<?php echo esc_attr($service_key); ?>][device]"
class="wptag-select" <?php disabled(!$can_edit); ?>>
<option value="all" <?php selected($service_settings['device'], 'all'); ?>>All Devices</option>
<option value="desktop" <?php selected($service_settings['device'], 'desktop'); ?>>Desktop Only</option>
<option value="mobile" <?php selected($service_settings['device'], 'mobile'); ?>>Mobile Only</option>
</select>
<div class="wptag-form-row">
<label class="wptag-form-label">Device</label>
<select name="wptag_settings[<?php echo esc_attr($service_key); ?>][device]"
class="wptag-select" <?php disabled(!$can_edit); ?>>
<option value="all" <?php selected($service_settings['device'], 'all'); ?>>All Devices</option>
<option value="desktop" <?php selected($service_settings['device'], 'desktop'); ?>>Desktop Only</option>
<option value="mobile" <?php selected($service_settings['device'], 'mobile'); ?>>Mobile Only</option>
</select>
</div>
</div>
</div>
<div class="wptag-advanced-toggle">
<button type="button" class="button button-link wptag-toggle-advanced">
<button type="button" class="wptag-toggle-advanced">
<span class="dashicons dashicons-arrow-down-alt2"></span>
Advanced Settings
</button>
@ -455,7 +509,7 @@ class Admin {
}
private function display_admin_notices() {
$notices = get_transient('wptag_admin_notices');
$notices = get_transient('wptag_admin_notices_' . get_current_user_id());
if (!$notices) {
return;
}
@ -468,25 +522,32 @@ class Admin {
);
}
delete_transient('wptag_admin_notices');
delete_transient('wptag_admin_notices_' . get_current_user_id());
}
private function add_admin_notice($type, $message) {
$notices = get_transient('wptag_admin_notices') ?: array();
$notices = get_transient('wptag_admin_notices_' . get_current_user_id()) ?: array();
$notices[] = array('type' => $type, 'message' => $message);
set_transient('wptag_admin_notices', $notices, 30);
set_transient('wptag_admin_notices_' . get_current_user_id(), $notices, 30);
}
public function handle_form_submission() {
if (!isset($_POST['wptag_nonce']) && !isset($_POST['wptag_services_nonce'])) {
return;
}
if (isset($_POST['wptag_services_nonce']) && wp_verify_nonce($_POST['wptag_services_nonce'], 'wptag_save_services')) {
$this->handle_services_form_submission();
return;
}
if (!isset($_POST['wptag_nonce']) || !wp_verify_nonce($_POST['wptag_nonce'], 'wptag_save_settings')) {
if (isset($_POST['wptag_nonce']) && wp_verify_nonce($_POST['wptag_nonce'], 'wptag_save_settings')) {
$this->handle_settings_form_submission();
return;
}
}
private function handle_settings_form_submission() {
if (!$this->current_user_can_manage_codes()) {
$this->add_admin_notice('error', 'You do not have permission to manage tracking codes.');
return;
@ -498,7 +559,21 @@ class Admin {
$result = $this->config->update_settings($settings);
if ($result) {
$this->add_admin_notice('success', 'Settings saved successfully.');
do_action('wptag_settings_saved', $settings);
$has_custom_code = false;
foreach ($settings as $service_settings) {
if (!empty($service_settings['enabled']) && empty($service_settings['use_template']) && !empty($service_settings['custom_code'])) {
$has_custom_code = true;
break;
}
}
if ($has_custom_code) {
$this->add_admin_notice('success', 'Settings saved successfully. Custom JavaScript codes have been preserved correctly.');
} else {
$this->add_admin_notice('success', 'Settings saved successfully.');
}
} else {
$this->add_admin_notice('error', 'Failed to save settings.');
}
@ -530,6 +605,7 @@ class Admin {
$result = $this->config->update_enabled_services($enabled_services);
if ($result) {
do_action('wptag_services_updated', $enabled_services);
$this->add_admin_notice('success', 'Services updated successfully.');
} else {
$this->add_admin_notice('error', 'Failed to update services.');
@ -555,8 +631,13 @@ class Admin {
return;
}
$service_key = sanitize_text_field($_POST['service']);
$use_template = $_POST['use_template'] === '1';
$service_key = sanitize_text_field($_POST['service'] ?? '');
$use_template = ($_POST['use_template'] ?? '0') === '1';
if (empty($service_key)) {
wp_send_json_error(array('message' => 'Service key is required.'));
return;
}
$settings = array(
'enabled' => true,
@ -567,22 +648,34 @@ class Admin {
$service_config = $this->config->get_service_config($service_key);
if ($service_config) {
$field_key = $service_config['field'];
$settings[$field_key] = sanitize_text_field($_POST['id_value']);
$settings[$field_key] = sanitize_text_field($_POST['id_value'] ?? '');
}
} else {
$settings['custom_code'] = wp_kses_post($_POST['custom_code']);
$raw_custom_code = $_POST['custom_code'] ?? '';
$settings['custom_code'] = $this->sanitize_custom_code_for_validation($raw_custom_code);
}
$is_valid = $this->validator->validate_service_code($service_key, $settings);
if ($is_valid) {
wp_send_json_success(array('message' => 'Code is valid'));
wp_send_json_success(array('message' => 'Code is valid and ready to use.'));
} else {
$errors = $this->validator->get_error_messages();
wp_send_json_error(array('message' => implode(', ', $errors)));
}
}
private function sanitize_custom_code_for_validation($custom_code) {
if (empty($custom_code)) {
return '';
}
$custom_code = stripslashes($custom_code);
$custom_code = wp_check_invalid_utf8($custom_code);
return trim($custom_code);
}
public function ajax_preview_code() {
check_ajax_referer('wptag_admin_nonce', 'nonce');
@ -591,18 +684,38 @@ class Admin {
return;
}
$service_key = sanitize_text_field($_POST['service']);
$id_value = sanitize_text_field($_POST['id_value']);
$service_key = sanitize_text_field($_POST['service'] ?? '');
$id_value = sanitize_text_field($_POST['id_value'] ?? '');
if (empty($service_key)) {
wp_send_json_error(array('message' => 'Service key is required.'));
return;
}
if (empty($id_value)) {
wp_send_json_error(array('message' => 'ID value is required for preview.'));
return;
}
$preview = $this->output_manager->get_template_preview($service_key, $id_value);
if (!empty($preview)) {
wp_send_json_success(array('preview' => $preview));
$safe_preview = $this->sanitize_preview_output($preview);
wp_send_json_success(array('preview' => $safe_preview));
} else {
wp_send_json_error(array('message' => 'Unable to generate preview'));
wp_send_json_error(array('message' => 'Unable to generate preview. Please check your ID format.'));
}
}
private function sanitize_preview_output($preview) {
$preview = htmlspecialchars($preview, ENT_QUOTES, 'UTF-8');
$preview = str_replace(array('&lt;script&gt;', '&lt;/script&gt;'), array('<script>', '</script>'), $preview);
$preview = str_replace(array('&lt;noscript&gt;', '&lt;/noscript&gt;'), array('<noscript>', '</noscript>'), $preview);
return $preview;
}
public function ajax_export_settings() {
check_ajax_referer('wptag_admin_nonce', 'nonce');
@ -611,12 +724,16 @@ class Admin {
return;
}
$export_data = $this->config->export_settings();
wp_send_json_success(array(
'data' => $export_data,
'filename' => 'wptag-settings-' . date('Y-m-d-H-i-s') . '.json'
));
try {
$export_data = $this->config->export_settings();
wp_send_json_success(array(
'data' => $export_data,
'filename' => 'wptag-settings-' . date('Y-m-d-H-i-s') . '.json'
));
} catch (Exception $e) {
wp_send_json_error(array('message' => 'Export failed: ' . $e->getMessage()));
}
}
public function ajax_import_settings() {
@ -627,19 +744,25 @@ class Admin {
return;
}
$import_data = stripslashes($_POST['import_data']);
$import_data = wp_unslash($_POST['import_data'] ?? '');
if (empty($import_data)) {
wp_send_json_error(array('message' => 'Import data is required.'));
return;
}
if ($this->validator->validate_import_data($import_data)) {
$result = $this->config->import_settings($import_data);
if (!is_wp_error($result)) {
do_action('wptag_settings_imported');
wp_send_json_success(array('message' => 'Settings imported successfully'));
} else {
wp_send_json_error(array('message' => $result->get_error_message()));
}
} else {
$errors = $this->validator->get_error_messages();
wp_send_json_error(array('message' => implode(', ', $errors)));
wp_send_json_error(array('message' => 'Import validation failed: ' . implode(', ', $errors)));
}
}
@ -651,9 +774,13 @@ class Admin {
return;
}
$this->config->reset_to_defaults();
wp_send_json_success(array('message' => 'Settings reset successfully'));
try {
$this->config->reset_to_defaults();
do_action('wptag_settings_reset');
wp_send_json_success(array('message' => 'Settings reset successfully'));
} catch (Exception $e) {
wp_send_json_error(array('message' => 'Reset failed: ' . $e->getMessage()));
}
}
public function add_action_links($links) {
@ -669,4 +796,36 @@ class Admin {
}
return $links;
}
private function get_template_help_text($service_key, $service_config) {
return '';
}
private function get_service_help_text($service_key, $service_config) {
return '';
}
private function get_service_docs_url($service_key) {
$service_docs_mapping = array(
'google_analytics' => 'google-analytics',
'google_tag_manager' => 'google-tag-manager',
'facebook_pixel' => 'facebook-pixel',
'google_ads' => 'google-ads',
'microsoft_clarity' => 'microsoft-clarity',
'hotjar' => 'hotjar',
'tiktok_pixel' => 'tiktok-pixel',
'linkedin_insight' => 'linkedin-insight',
'twitter_pixel' => 'twitter-pixel',
'pinterest_pixel' => 'pinterest-pixel',
'snapchat_pixel' => 'snapchat-pixel',
'google_optimize' => 'google-optimize',
'crazyegg' => 'crazy-egg',
'mixpanel' => 'mixpanel',
'amplitude' => 'amplitude',
'matomo' => 'matomo'
);
$docs_slug = isset($service_docs_mapping[$service_key]) ? $service_docs_mapping[$service_key] : $service_key;
return 'https://wptag.com/document/' . $docs_slug . '/';
}
}