Add initial WPTag admin and core plugin files

Introduce the main admin controllers, AJAX handlers, UI partials, and core classes for the WPTag plugin. This includes admin interface components, dashboard, settings, snippet management, asset files, and localization support. These files establish the foundation for managing code snippets, templates, and plugin settings within the WordPress admin.
This commit is contained in:
feibisi 2025-07-16 03:44:42 +08:00
parent 6524694731
commit 840a779a84
20 changed files with 4389 additions and 0 deletions

View file

@ -0,0 +1,301 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPTag_Admin_Controller {
private $snippet_manager;
private $template_manager;
private $page_hook_suffix = [];
public function __construct($snippet_manager, $template_manager) {
$this->snippet_manager = $snippet_manager;
$this->template_manager = $template_manager;
add_action('admin_menu', [$this, 'add_admin_menu']);
add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']);
add_action('admin_init', [$this, 'handle_form_submissions']);
add_action('admin_notices', [$this, 'display_admin_notices']);
}
public function add_admin_menu() {
$capability = 'manage_options';
$this->page_hook_suffix['main'] = add_menu_page(
__('WPTAG', 'wptag'),
__('WPTAG', 'wptag'),
$capability,
'wptag',
[$this, 'render_dashboard_page'],
'dashicons-code-standards',
85
);
$this->page_hook_suffix['dashboard'] = add_submenu_page(
'wptag',
__('Dashboard', 'wptag'),
__('Dashboard', 'wptag'),
$capability,
'wptag',
[$this, 'render_dashboard_page']
);
$this->page_hook_suffix['snippets'] = add_submenu_page(
'wptag',
__('Code Snippets', 'wptag'),
__('Code Snippets', 'wptag'),
$capability,
'wptag-snippets',
[$this, 'render_snippets_page']
);
$this->page_hook_suffix['templates'] = add_submenu_page(
'wptag',
__('Service Templates', 'wptag'),
__('Service Templates', 'wptag'),
$capability,
'wptag-templates',
[$this, 'render_templates_page']
);
$this->page_hook_suffix['settings'] = add_submenu_page(
'wptag',
__('Settings', 'wptag'),
__('Settings', 'wptag'),
$capability,
'wptag-settings',
[$this, 'render_settings_page']
);
}
public function enqueue_admin_assets($hook) {
if (!in_array($hook, $this->page_hook_suffix)) {
return;
}
wp_enqueue_style(
'wptag-admin',
WPTAG_PLUGIN_URL . 'assets/css/admin.css',
['wp-components'],
WPTAG_VERSION
);
wp_enqueue_script(
'wptag-admin',
WPTAG_PLUGIN_URL . 'assets/js/admin.js',
['jquery', 'wp-api', 'wp-i18n', 'wp-components', 'wp-element'],
WPTAG_VERSION,
true
);
wp_localize_script('wptag-admin', 'wptagAdmin', [
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('wptag_admin'),
'strings' => [
'confirmDelete' => __('Are you sure you want to delete this snippet?', 'wptag'),
'saved' => __('Settings saved successfully.', 'wptag'),
'error' => __('An error occurred. Please try again.', 'wptag')
]
]);
if (isset($_GET['action']) && ($_GET['action'] === 'new' || $_GET['action'] === 'edit')) {
wp_enqueue_code_editor(['type' => 'text/html']);
}
}
public function render_dashboard_page() {
require_once WPTAG_PLUGIN_DIR . 'admin/partials/dashboard.php';
}
public function render_snippets_page() {
require_once WPTAG_PLUGIN_DIR . 'admin/partials/snippets.php';
}
public function render_templates_page() {
require_once WPTAG_PLUGIN_DIR . 'admin/partials/templates.php';
}
public function render_settings_page() {
require_once WPTAG_PLUGIN_DIR . 'admin/partials/settings.php';
}
public function handle_form_submissions() {
if (!isset($_POST['wptag_action'])) {
return;
}
$action = sanitize_key($_POST['wptag_action']);
if (!wp_verify_nonce($_POST['_wpnonce'], 'wptag_' . $action)) {
wp_die(__('Security check failed', 'wptag'));
}
if (!current_user_can('manage_options')) {
wp_die(__('Permission denied', 'wptag'));
}
switch ($action) {
case 'create_snippet':
$this->handle_create_snippet();
break;
case 'update_snippet':
$this->handle_update_snippet();
break;
case 'delete_snippet':
$this->handle_delete_snippet();
break;
case 'toggle_snippet':
$this->handle_toggle_snippet();
break;
case 'save_settings':
$this->handle_save_settings();
break;
}
}
private function handle_create_snippet() {
$data = [
'name' => $_POST['name'] ?? '',
'description' => $_POST['description'] ?? '',
'code' => $_POST['code'] ?? '',
'code_type' => $_POST['code_type'] ?? 'html',
'position' => $_POST['position'] ?? 'head',
'category' => $_POST['category'] ?? 'custom',
'priority' => $_POST['priority'] ?? 10,
'status' => isset($_POST['status']) ? 1 : 0,
'device_type' => $_POST['device_type'] ?? 'all',
'load_method' => $_POST['load_method'] ?? 'normal',
'conditions' => $this->parse_conditions($_POST['conditions'] ?? [])
];
$result = $this->snippet_manager->create_snippet($data);
if (is_wp_error($result)) {
$this->add_admin_notice($result->get_error_message(), 'error');
} else {
$this->add_admin_notice(__('Snippet created successfully', 'wptag'), 'success');
wp_redirect(admin_url('admin.php?page=wptag-snippets'));
exit;
}
}
private function handle_update_snippet() {
$id = intval($_POST['snippet_id'] ?? 0);
if (!$id) {
$this->add_admin_notice(__('Invalid snippet ID', 'wptag'), 'error');
return;
}
$data = [
'name' => $_POST['name'] ?? '',
'description' => $_POST['description'] ?? '',
'code' => $_POST['code'] ?? '',
'code_type' => $_POST['code_type'] ?? 'html',
'position' => $_POST['position'] ?? 'head',
'category' => $_POST['category'] ?? 'custom',
'priority' => $_POST['priority'] ?? 10,
'status' => isset($_POST['status']) ? 1 : 0,
'device_type' => $_POST['device_type'] ?? 'all',
'load_method' => $_POST['load_method'] ?? 'normal',
'conditions' => $this->parse_conditions($_POST['conditions'] ?? [])
];
$result = $this->snippet_manager->update_snippet($id, $data);
if (is_wp_error($result)) {
$this->add_admin_notice($result->get_error_message(), 'error');
} else {
$this->add_admin_notice(__('Snippet updated successfully', 'wptag'), 'success');
}
}
private function handle_delete_snippet() {
$id = intval($_GET['snippet_id'] ?? 0);
if (!$id) {
$this->add_admin_notice(__('Invalid snippet ID', 'wptag'), 'error');
return;
}
$result = $this->snippet_manager->delete_snippet($id);
if (is_wp_error($result)) {
$this->add_admin_notice($result->get_error_message(), 'error');
} else {
$this->add_admin_notice(__('Snippet deleted successfully', 'wptag'), 'success');
}
wp_redirect(admin_url('admin.php?page=wptag-snippets'));
exit;
}
private function handle_toggle_snippet() {
$id = intval($_GET['snippet_id'] ?? 0);
if (!$id) {
wp_die(json_encode(['success' => false, 'message' => 'Invalid ID']));
}
$result = $this->snippet_manager->toggle_status($id);
if (is_wp_error($result)) {
wp_die(json_encode(['success' => false, 'message' => $result->get_error_message()]));
}
wp_die(json_encode(['success' => true, 'status' => $result]));
}
private function handle_save_settings() {
$settings = [
'enable_cache' => isset($_POST['enable_cache']) ? 1 : 0,
'cache_ttl' => intval($_POST['cache_ttl'] ?? 3600),
'enable_debug' => isset($_POST['enable_debug']) ? 1 : 0,
'cleanup_on_uninstall' => isset($_POST['cleanup_on_uninstall']) ? 1 : 0
];
update_option('wptag_settings', $settings);
$this->add_admin_notice(__('Settings saved successfully', 'wptag'), 'success');
wp_redirect(admin_url('admin.php?page=wptag-settings'));
exit;
}
private function parse_conditions($conditions_data) {
if (empty($conditions_data) || !is_array($conditions_data)) {
return null;
}
return $conditions_data;
}
private function add_admin_notice($message, $type = 'info') {
set_transient('wptag_admin_notice', [
'message' => $message,
'type' => $type
], 30);
}
public function display_admin_notices() {
$notice = get_transient('wptag_admin_notice');
if (!$notice) {
return;
}
delete_transient('wptag_admin_notice');
$class = 'notice notice-' . esc_attr($notice['type']);
printf(
'<div class="%1$s is-dismissible"><p>%2$s</p></div>',
$class,
esc_html($notice['message'])
);
}
}

View file

@ -0,0 +1,195 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPTag_Admin_Interface {
public static function render_field($field) {
$type = $field['type'] ?? 'text';
$name = $field['name'] ?? '';
$label = $field['label'] ?? '';
$value = $field['value'] ?? '';
$options = $field['options'] ?? [];
$description = $field['description'] ?? '';
$required = $field['required'] ?? false;
if ($label) {
echo '<label for="' . esc_attr($name) . '">' . esc_html($label);
if ($required) {
echo ' <span class="required">*</span>';
}
echo '</label>';
}
switch ($type) {
case 'text':
case 'email':
case 'url':
case 'number':
self::render_input_field($type, $name, $value, $required);
break;
case 'textarea':
self::render_textarea_field($name, $value, $required);
break;
case 'select':
self::render_select_field($name, $value, $options, $required);
break;
case 'checkbox':
self::render_checkbox_field($name, $value, $label);
break;
case 'radio':
self::render_radio_field($name, $value, $options);
break;
}
if ($description) {
echo '<p class="description">' . esc_html($description) . '</p>';
}
}
private static function render_input_field($type, $name, $value, $required) {
printf(
'<input type="%s" id="%s" name="%s" value="%s" class="regular-text" %s />',
esc_attr($type),
esc_attr($name),
esc_attr($name),
esc_attr($value),
$required ? 'required' : ''
);
}
private static function render_textarea_field($name, $value, $required) {
printf(
'<textarea id="%s" name="%s" rows="5" cols="50" class="large-text" %s>%s</textarea>',
esc_attr($name),
esc_attr($name),
$required ? 'required' : '',
esc_textarea($value)
);
}
private static function render_select_field($name, $value, $options, $required) {
printf(
'<select id="%s" name="%s" %s>',
esc_attr($name),
esc_attr($name),
$required ? 'required' : ''
);
foreach ($options as $option_value => $option_label) {
printf(
'<option value="%s" %s>%s</option>',
esc_attr($option_value),
selected($value, $option_value, false),
esc_html($option_label)
);
}
echo '</select>';
}
private static function render_checkbox_field($name, $value, $label) {
printf(
'<label><input type="checkbox" id="%s" name="%s" value="1" %s /> %s</label>',
esc_attr($name),
esc_attr($name),
checked($value, 1, false),
esc_html($label)
);
}
private static function render_radio_field($name, $value, $options) {
foreach ($options as $option_value => $option_label) {
printf(
'<label><input type="radio" name="%s" value="%s" %s /> %s</label><br>',
esc_attr($name),
esc_attr($option_value),
checked($value, $option_value, false),
esc_html($option_label)
);
}
}
public static function render_modal($id, $title, $content, $footer = '') {
?>
<div id="<?php echo esc_attr($id); ?>" class="wptag-modal" style="display: none;">
<div class="wptag-modal-content">
<div class="wptag-modal-header">
<h2><?php echo esc_html($title); ?></h2>
<button type="button" class="wptag-modal-close" aria-label="<?php esc_attr_e('Close', 'wptag'); ?>">
<span class="dashicons dashicons-no-alt"></span>
</button>
</div>
<div class="wptag-modal-body">
<?php echo $content; ?>
</div>
<?php if ($footer) : ?>
<div class="wptag-modal-footer">
<?php echo $footer; ?>
</div>
<?php endif; ?>
</div>
</div>
<?php
}
public static function render_notice($message, $type = 'info') {
$classes = [
'info' => 'notice-info',
'success' => 'notice-success',
'warning' => 'notice-warning',
'error' => 'notice-error'
];
$class = $classes[$type] ?? 'notice-info';
printf(
'<div class="notice %s is-dismissible"><p>%s</p></div>',
esc_attr($class),
esc_html($message)
);
}
public static function render_tabs($tabs, $current_tab) {
echo '<h2 class="nav-tab-wrapper">';
foreach ($tabs as $tab_key => $tab_label) {
$class = ($tab_key === $current_tab) ? ' nav-tab-active' : '';
printf(
'<a href="?page=%s&tab=%s" class="nav-tab%s">%s</a>',
esc_attr($_GET['page']),
esc_attr($tab_key),
esc_attr($class),
esc_html($tab_label)
);
}
echo '</h2>';
}
public static function render_action_buttons($actions) {
echo '<div class="wptag-action-buttons">';
foreach ($actions as $action) {
$class = $action['primary'] ?? false ? 'button-primary' : 'button-secondary';
$url = $action['url'] ?? '#';
$onclick = $action['onclick'] ?? '';
printf(
'<a href="%s" class="button %s" %s>%s</a> ',
esc_url($url),
esc_attr($class),
$onclick ? 'onclick="' . esc_attr($onclick) . '"' : '',
esc_html($action['label'])
);
}
echo '</div>';
}
}

View file

@ -0,0 +1,322 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPTag_Ajax_Handler {
private $snippet_manager;
private $template_manager;
public function __construct($snippet_manager, $template_manager) {
$this->snippet_manager = $snippet_manager;
$this->template_manager = $template_manager;
$this->register_ajax_handlers();
}
private function register_ajax_handlers() {
$actions = [
'wptag_toggle_snippet',
'wptag_delete_snippet',
'wptag_search_snippets',
'wptag_validate_code',
'wptag_preview_snippet',
'wptag_get_template',
'wptag_process_template',
'wptag_export_snippets',
'wptag_import_snippets',
'wptag_clear_cache'
];
foreach ($actions as $action) {
add_action('wp_ajax_' . $action, [$this, 'handle_' . str_replace('wptag_', '', $action)]);
}
}
public function handle_toggle_snippet() {
$this->verify_ajax_request();
$snippet_id = intval($_POST['snippet_id'] ?? 0);
if (!$snippet_id) {
wp_send_json_error(['message' => __('Invalid snippet ID', 'wptag')]);
}
$result = $this->snippet_manager->toggle_status($snippet_id);
if (is_wp_error($result)) {
wp_send_json_error(['message' => $result->get_error_message()]);
}
wp_send_json_success([
'status' => $result,
'message' => $result ? __('Snippet enabled', 'wptag') : __('Snippet disabled', 'wptag')
]);
}
public function handle_delete_snippet() {
$this->verify_ajax_request();
$snippet_id = intval($_POST['snippet_id'] ?? 0);
if (!$snippet_id) {
wp_send_json_error(['message' => __('Invalid snippet ID', 'wptag')]);
}
$result = $this->snippet_manager->delete_snippet($snippet_id);
if (is_wp_error($result)) {
wp_send_json_error(['message' => $result->get_error_message()]);
}
wp_send_json_success(['message' => __('Snippet deleted successfully', 'wptag')]);
}
public function handle_search_snippets() {
$this->verify_ajax_request();
$args = [
'search' => sanitize_text_field($_POST['search'] ?? ''),
'category' => sanitize_key($_POST['category'] ?? ''),
'position' => sanitize_key($_POST['position'] ?? ''),
'status' => isset($_POST['status']) ? intval($_POST['status']) : null,
'per_page' => intval($_POST['per_page'] ?? 20),
'page' => intval($_POST['page'] ?? 1)
];
$snippets = $this->snippet_manager->get_snippets($args);
wp_send_json_success(['snippets' => $snippets]);
}
public function handle_validate_code() {
$this->verify_ajax_request();
$code = $_POST['code'] ?? '';
$code_type = sanitize_key($_POST['code_type'] ?? 'html');
$errors = [];
switch ($code_type) {
case 'javascript':
$errors = $this->validate_javascript($code);
break;
case 'css':
$errors = $this->validate_css($code);
break;
case 'html':
$errors = $this->validate_html($code);
break;
}
if (empty($errors)) {
wp_send_json_success(['message' => __('Code is valid', 'wptag')]);
} else {
wp_send_json_error(['errors' => $errors]);
}
}
public function handle_preview_snippet() {
$this->verify_ajax_request();
$snippet_id = intval($_POST['snippet_id'] ?? 0);
if (!$snippet_id) {
wp_send_json_error(['message' => __('Invalid snippet ID', 'wptag')]);
}
$snippet = $this->snippet_manager->get_snippet($snippet_id);
if (!$snippet) {
wp_send_json_error(['message' => __('Snippet not found', 'wptag')]);
}
$preview_url = add_query_arg([
'wptag_preview' => 1,
'snippet_id' => $snippet_id
], home_url());
wp_send_json_success(['preview_url' => $preview_url]);
}
public function handle_get_template() {
$this->verify_ajax_request();
$service_type = sanitize_key($_POST['service_type'] ?? '');
if (!$service_type) {
wp_send_json_error(['message' => __('Invalid service type', 'wptag')]);
}
$template = $this->template_manager->get_template($service_type);
if (!$template) {
wp_send_json_error(['message' => __('Template not found', 'wptag')]);
}
wp_send_json_success(['template' => $template]);
}
public function handle_process_template() {
$this->verify_ajax_request();
$service_type = sanitize_key($_POST['service_type'] ?? '');
$config_data = $_POST['config'] ?? [];
if (!$service_type) {
wp_send_json_error(['message' => __('Invalid service type', 'wptag')]);
}
$result = $this->template_manager->process_template_config($service_type, $config_data);
if (is_wp_error($result)) {
wp_send_json_error(['message' => $result->get_error_message()]);
}
$snippet_data = array_merge($result, [
'description' => sprintf(__('Generated from %s template', 'wptag'), $result['name']),
'status' => 1
]);
$snippet_id = $this->snippet_manager->create_snippet($snippet_data);
if (is_wp_error($snippet_id)) {
wp_send_json_error(['message' => $snippet_id->get_error_message()]);
}
wp_send_json_success([
'snippet_id' => $snippet_id,
'message' => __('Snippet created successfully from template', 'wptag')
]);
}
public function handle_export_snippets() {
$this->verify_ajax_request();
$snippet_ids = array_map('intval', $_POST['snippet_ids'] ?? []);
$export_data = [
'version' => WPTAG_VERSION,
'exported_at' => current_time('mysql'),
'snippets' => []
];
foreach ($snippet_ids as $id) {
$snippet = $this->snippet_manager->get_snippet($id);
if ($snippet) {
unset($snippet['id'], $snippet['created_by'], $snippet['last_modified_by']);
$export_data['snippets'][] = $snippet;
}
}
wp_send_json_success([
'filename' => 'wptag-export-' . date('Y-m-d') . '.json',
'data' => json_encode($export_data, JSON_PRETTY_PRINT)
]);
}
public function handle_import_snippets() {
$this->verify_ajax_request();
$import_data = json_decode(stripslashes($_POST['import_data'] ?? ''), true);
if (!$import_data || !isset($import_data['snippets'])) {
wp_send_json_error(['message' => __('Invalid import data', 'wptag')]);
}
$imported = 0;
$errors = [];
foreach ($import_data['snippets'] as $snippet) {
$result = $this->snippet_manager->create_snippet($snippet);
if (is_wp_error($result)) {
$errors[] = sprintf(
__('Failed to import "%s": %s', 'wptag'),
$snippet['name'],
$result->get_error_message()
);
} else {
$imported++;
}
}
if ($imported > 0) {
$message = sprintf(
_n('%d snippet imported successfully', '%d snippets imported successfully', $imported, 'wptag'),
$imported
);
if (!empty($errors)) {
$message .= ' ' . __('Some snippets failed to import.', 'wptag');
}
wp_send_json_success([
'message' => $message,
'imported' => $imported,
'errors' => $errors
]);
} else {
wp_send_json_error([
'message' => __('No snippets were imported', 'wptag'),
'errors' => $errors
]);
}
}
public function handle_clear_cache() {
$this->verify_ajax_request();
$cache_manager = new WPTag_Cache_Manager();
$cache_manager->flush();
wp_send_json_success(['message' => __('Cache cleared successfully', 'wptag')]);
}
private function verify_ajax_request() {
if (!check_ajax_referer('wptag_admin', 'nonce', false)) {
wp_send_json_error(['message' => __('Security check failed', 'wptag')]);
}
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => __('Permission denied', 'wptag')]);
}
}
private function validate_javascript($code) {
$errors = [];
if (preg_match('/\bdocument\.write\b/i', $code)) {
$errors[] = __('document.write is not recommended', 'wptag');
}
if (preg_match('/\beval\s*\(/i', $code)) {
$errors[] = __('eval() is potentially dangerous', 'wptag');
}
return $errors;
}
private function validate_css($code) {
$errors = [];
if (preg_match('/@import\s+url/i', $code)) {
$errors[] = __('@import may affect performance', 'wptag');
}
return $errors;
}
private function validate_html($code) {
$errors = [];
if (preg_match('/<script[^>]*src=["\'](?!https?:\/\/)/i', $code)) {
$errors[] = __('Use absolute URLs for external scripts', 'wptag');
}
return $errors;
}
}

View file

@ -0,0 +1,177 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
global $wpdb;
$snippets_table = $wpdb->prefix . 'wptag_snippets';
$logs_table = $wpdb->prefix . 'wptag_logs';
$total_snippets = $wpdb->get_var("SELECT COUNT(*) FROM $snippets_table");
$active_snippets = $wpdb->get_var("SELECT COUNT(*) FROM $snippets_table WHERE status = 1");
$categories_count = $wpdb->get_results("SELECT category, COUNT(*) as count FROM $snippets_table GROUP BY category", ARRAY_A);
$recent_activity = $wpdb->get_results(
"SELECT l.*, u.display_name
FROM $logs_table l
LEFT JOIN {$wpdb->users} u ON l.user_id = u.ID
ORDER BY l.created_at DESC
LIMIT 10",
ARRAY_A
);
$categories = $this->snippet_manager->get_categories();
?>
<div class="wrap wptag-admin-wrap">
<div class="wptag-header">
<h1><?php _e('WPTAG Dashboard', 'wptag'); ?></h1>
</div>
<div class="wptag-content">
<div class="wptag-stats-grid">
<div class="wptag-stat-card">
<h3><?php _e('Total Snippets', 'wptag'); ?></h3>
<div class="stat-value"><?php echo number_format($total_snippets); ?></div>
</div>
<div class="wptag-stat-card">
<h3><?php _e('Active Snippets', 'wptag'); ?></h3>
<div class="stat-value"><?php echo number_format($active_snippets); ?></div>
</div>
<div class="wptag-stat-card">
<h3><?php _e('Inactive Snippets', 'wptag'); ?></h3>
<div class="stat-value"><?php echo number_format($total_snippets - $active_snippets); ?></div>
</div>
<div class="wptag-stat-card">
<h3><?php _e('Success Rate', 'wptag'); ?></h3>
<div class="stat-value">
<?php
$success_rate = $total_snippets > 0 ? round(($active_snippets / $total_snippets) * 100) : 0;
echo $success_rate . '%';
?>
</div>
</div>
</div>
<div class="wptag-dashboard-row">
<div class="wptag-dashboard-col">
<h2><?php _e('Snippets by Category', 'wptag'); ?></h2>
<table class="wptag-table">
<thead>
<tr>
<th><?php _e('Category', 'wptag'); ?></th>
<th><?php _e('Count', 'wptag'); ?></th>
</tr>
</thead>
<tbody>
<?php if (empty($categories_count)) : ?>
<tr>
<td colspan="2"><?php _e('No snippets yet', 'wptag'); ?></td>
</tr>
<?php else : ?>
<?php foreach ($categories_count as $cat) : ?>
<tr>
<td><?php echo esc_html($categories[$cat['category']] ?? $cat['category']); ?></td>
<td><?php echo number_format($cat['count']); ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<div class="wptag-dashboard-col">
<h2><?php _e('Quick Actions', 'wptag'); ?></h2>
<div class="wptag-quick-actions">
<a href="<?php echo admin_url('admin.php?page=wptag-snippets&action=new'); ?>" class="button button-primary">
<?php _e('Create New Snippet', 'wptag'); ?>
</a>
<a href="<?php echo admin_url('admin.php?page=wptag-templates'); ?>" class="button">
<?php _e('Browse Templates', 'wptag'); ?>
</a>
<a href="<?php echo admin_url('admin.php?page=wptag-settings'); ?>" class="button">
<?php _e('Settings', 'wptag'); ?>
</a>
</div>
<h3><?php _e('Getting Started', 'wptag'); ?></h3>
<ol>
<li><?php _e('Create a new snippet or use a template', 'wptag'); ?></li>
<li><?php _e('Configure where and when it should appear', 'wptag'); ?></li>
<li><?php _e('Activate the snippet to make it live', 'wptag'); ?></li>
<li><?php _e('Monitor performance and adjust as needed', 'wptag'); ?></li>
</ol>
</div>
</div>
<h2><?php _e('Recent Activity', 'wptag'); ?></h2>
<table class="wptag-table">
<thead>
<tr>
<th><?php _e('User', 'wptag'); ?></th>
<th><?php _e('Action', 'wptag'); ?></th>
<th><?php _e('Object', 'wptag'); ?></th>
<th><?php _e('Date', 'wptag'); ?></th>
</tr>
</thead>
<tbody>
<?php if (empty($recent_activity)) : ?>
<tr>
<td colspan="4"><?php _e('No recent activity', 'wptag'); ?></td>
</tr>
<?php else : ?>
<?php foreach ($recent_activity as $activity) : ?>
<tr>
<td><?php echo esc_html($activity['display_name'] ?? __('Unknown', 'wptag')); ?></td>
<td><?php echo esc_html(ucfirst($activity['action'])); ?></td>
<td>
<?php
echo esc_html(ucfirst($activity['object_type']));
if ($activity['object_id']) {
echo ' #' . $activity['object_id'];
}
?>
</td>
<td><?php echo human_time_diff(strtotime($activity['created_at']), current_time('timestamp')) . ' ' . __('ago', 'wptag'); ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<style>
.wptag-dashboard-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
margin: 30px 0;
}
.wptag-dashboard-col h2 {
margin-top: 0;
}
.wptag-quick-actions {
display: flex;
gap: 10px;
margin: 20px 0;
flex-wrap: wrap;
}
.wptag-quick-actions .button {
flex: 1;
text-align: center;
min-width: 150px;
}
@media (max-width: 782px) {
.wptag-dashboard-row {
grid-template-columns: 1fr;
}
}
</style>

245
admin/partials/settings.php Normal file
View file

@ -0,0 +1,245 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
$settings = get_option('wptag_settings', [
'enable_cache' => 1,
'cache_ttl' => 3600,
'enable_debug' => 0,
'cleanup_on_uninstall' => 0
]);
$cache_manager = new WPTag_Cache_Manager();
$cache_stats = $cache_manager->get_cache_stats();
?>
<div class="wrap wptag-admin-wrap">
<div class="wptag-header">
<h1><?php _e('WPTAG Settings', 'wptag'); ?></h1>
</div>
<div class="wptag-content">
<form method="post" action="<?php echo admin_url('admin.php?page=wptag-settings'); ?>">
<?php wp_nonce_field('wptag_save_settings'); ?>
<input type="hidden" name="wptag_action" value="save_settings">
<h2><?php _e('Cache Settings', 'wptag'); ?></h2>
<table class="wptag-form-table">
<tr>
<th scope="row"><?php _e('Enable Cache', 'wptag'); ?></th>
<td>
<label>
<input type="checkbox" name="enable_cache" value="1" <?php checked($settings['enable_cache'], 1); ?>>
<?php _e('Enable caching for better performance', 'wptag'); ?>
</label>
<p class="description">
<?php _e('Caching stores processed snippets and conditions to improve page load times.', 'wptag'); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="cache_ttl"><?php _e('Cache Duration', 'wptag'); ?></label>
</th>
<td>
<input type="number" id="cache_ttl" name="cache_ttl" value="<?php echo esc_attr($settings['cache_ttl']); ?>" min="60" max="86400">
<span><?php _e('seconds', 'wptag'); ?></span>
<p class="description">
<?php _e('How long to keep cached data. Default is 3600 seconds (1 hour).', 'wptag'); ?>
</p>
</td>
</tr>
<tr>
<th scope="row"><?php _e('Cache Status', 'wptag'); ?></th>
<td>
<div class="wptag-cache-stats">
<p>
<strong><?php _e('Cache Enabled:', 'wptag'); ?></strong>
<?php echo $cache_stats['enabled'] ? __('Yes', 'wptag') : __('No', 'wptag'); ?>
</p>
<p>
<strong><?php _e('TTL:', 'wptag'); ?></strong>
<?php echo number_format($cache_stats['ttl']); ?> <?php _e('seconds', 'wptag'); ?>
</p>
</div>
<p>
<button type="button" class="button" id="clear-cache">
<?php _e('Clear Cache Now', 'wptag'); ?>
</button>
</p>
</td>
</tr>
</table>
<h2><?php _e('Debug Settings', 'wptag'); ?></h2>
<table class="wptag-form-table">
<tr>
<th scope="row"><?php _e('Debug Mode', 'wptag'); ?></th>
<td>
<label>
<input type="checkbox" name="enable_debug" value="1" <?php checked($settings['enable_debug'], 1); ?>>
<?php _e('Enable debug mode', 'wptag'); ?>
</label>
<p class="description">
<?php _e('Show additional information in HTML comments for troubleshooting.', 'wptag'); ?>
</p>
</td>
</tr>
</table>
<h2><?php _e('Uninstall Settings', 'wptag'); ?></h2>
<table class="wptag-form-table">
<tr>
<th scope="row"><?php _e('Data Cleanup', 'wptag'); ?></th>
<td>
<label>
<input type="checkbox" name="cleanup_on_uninstall" value="1" <?php checked($settings['cleanup_on_uninstall'], 1); ?>>
<?php _e('Remove all data when uninstalling the plugin', 'wptag'); ?>
</label>
<p class="description">
<?php _e('Warning: This will permanently delete all snippets, templates, and settings when the plugin is uninstalled.', 'wptag'); ?>
</p>
</td>
</tr>
</table>
<h2><?php _e('Import/Export', 'wptag'); ?></h2>
<table class="wptag-form-table">
<tr>
<th scope="row"><?php _e('Export Snippets', 'wptag'); ?></th>
<td>
<p><?php _e('Export all your snippets to a JSON file for backup or migration.', 'wptag'); ?></p>
<button type="button" class="button" id="export-all-snippets">
<?php _e('Export All Snippets', 'wptag'); ?>
</button>
</td>
</tr>
<tr>
<th scope="row"><?php _e('Import Snippets', 'wptag'); ?></th>
<td>
<p><?php _e('Import snippets from a JSON file.', 'wptag'); ?></p>
<input type="file" id="import-file" accept=".json">
<button type="button" class="button" id="import-snippets">
<?php _e('Import Snippets', 'wptag'); ?>
</button>
</td>
</tr>
</table>
<p class="submit">
<button type="submit" class="button button-primary">
<?php _e('Save Settings', 'wptag'); ?>
</button>
</p>
</form>
</div>
</div>
<script>
jQuery(document).ready(function($) {
$('#clear-cache').on('click', function() {
const $button = $(this);
$button.prop('disabled', true);
$.ajax({
url: wptagAdmin.ajaxUrl,
type: 'POST',
data: {
action: 'wptag_clear_cache',
nonce: wptagAdmin.nonce
},
success: function(response) {
if (response.success) {
alert(response.data.message);
} else {
alert(response.data.message || wptagAdmin.strings.error);
}
},
complete: function() {
$button.prop('disabled', false);
}
});
});
$('#export-all-snippets').on('click', function() {
$.ajax({
url: wptagAdmin.ajaxUrl,
type: 'POST',
data: {
action: 'wptag_export_snippets',
snippet_ids: [],
nonce: wptagAdmin.nonce
},
success: function(response) {
if (response.success) {
const blob = new Blob([response.data.data], { type: 'application/json' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = response.data.filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
}
}
});
});
$('#import-snippets').on('click', function() {
const fileInput = document.getElementById('import-file');
const file = fileInput.files[0];
if (!file) {
alert('Please select a file to import');
return;
}
const reader = new FileReader();
reader.onload = function(e) {
$.ajax({
url: wptagAdmin.ajaxUrl,
type: 'POST',
data: {
action: 'wptag_import_snippets',
import_data: e.target.result,
nonce: wptagAdmin.nonce
},
success: function(response) {
if (response.success) {
alert(response.data.message);
if (response.data.imported > 0) {
window.location.reload();
}
} else {
alert(response.data.message || wptagAdmin.strings.error);
}
}
});
};
reader.readAsText(file);
});
});
</script>
<style>
.wptag-cache-stats {
background: #f0f0f1;
border: 1px solid #c3c4c7;
border-radius: 4px;
padding: 10px 15px;
margin-bottom: 15px;
}
.wptag-cache-stats p {
margin: 5px 0;
}
#import-file {
margin-right: 10px;
}
</style>

View file

@ -0,0 +1,207 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
$snippet = null;
$is_edit = false;
if ($action === 'edit' && $snippet_id) {
$snippet = $this->snippet_manager->get_snippet($snippet_id);
if (!$snippet) {
wp_die(__('Snippet not found', 'wptag'));
}
$is_edit = true;
}
$categories = $this->snippet_manager->get_categories();
$positions = $this->snippet_manager->get_positions();
?>
<div class="wrap wptag-admin-wrap">
<div class="wptag-header">
<h1>
<?php echo $is_edit ? __('Edit Snippet', 'wptag') : __('Add New Snippet', 'wptag'); ?>
<a href="<?php echo admin_url('admin.php?page=wptag-snippets'); ?>" class="page-title-action">
<?php _e('Back to Snippets', 'wptag'); ?>
</a>
</h1>
</div>
<div class="wptag-content">
<form method="post" action="<?php echo admin_url('admin.php?page=wptag-snippets'); ?>" class="wptag-snippet-form">
<?php wp_nonce_field($is_edit ? 'wptag_update_snippet' : 'wptag_create_snippet'); ?>
<input type="hidden" name="wptag_action" value="<?php echo $is_edit ? 'update_snippet' : 'create_snippet'; ?>">
<?php if ($is_edit) : ?>
<input type="hidden" name="snippet_id" value="<?php echo $snippet_id; ?>">
<?php endif; ?>
<table class="wptag-form-table">
<tr>
<th scope="row">
<label for="snippet-name"><?php _e('Name', 'wptag'); ?> <span class="required">*</span></label>
</th>
<td>
<input type="text" id="snippet-name" name="name" value="<?php echo esc_attr($snippet['name'] ?? ''); ?>" required>
<p class="description"><?php _e('Give your snippet a descriptive name', 'wptag'); ?></p>
</td>
</tr>
<tr>
<th scope="row">
<label for="snippet-description"><?php _e('Description', 'wptag'); ?></label>
</th>
<td>
<textarea id="snippet-description" name="description" rows="3"><?php echo esc_textarea($snippet['description'] ?? ''); ?></textarea>
<p class="description"><?php _e('Optional description to help you remember what this snippet does', 'wptag'); ?></p>
</td>
</tr>
<tr>
<th scope="row">
<label for="code_type"><?php _e('Code Type', 'wptag'); ?></label>
</th>
<td>
<select id="code_type" name="code_type">
<option value="html" <?php selected($snippet['code_type'] ?? 'html', 'html'); ?>><?php _e('HTML', 'wptag'); ?></option>
<option value="javascript" <?php selected($snippet['code_type'] ?? '', 'javascript'); ?>><?php _e('JavaScript', 'wptag'); ?></option>
<option value="css" <?php selected($snippet['code_type'] ?? '', 'css'); ?>><?php _e('CSS', 'wptag'); ?></option>
</select>
</td>
</tr>
<tr>
<th scope="row">
<label for="snippet-code"><?php _e('Code', 'wptag'); ?> <span class="required">*</span></label>
</th>
<td>
<textarea id="snippet-code" name="code" class="wptag-code-editor" required><?php echo esc_textarea($snippet['code'] ?? ''); ?></textarea>
<p class="description"><?php _e('Enter your code here. It will be inserted exactly as you type it.', 'wptag'); ?></p>
</td>
</tr>
<tr>
<th scope="row">
<label for="position"><?php _e('Position', 'wptag'); ?></label>
</th>
<td>
<select id="position" name="position">
<?php foreach ($positions as $key => $label) : ?>
<option value="<?php echo esc_attr($key); ?>" <?php selected($snippet['position'] ?? 'head', $key); ?>>
<?php echo esc_html($label); ?>
</option>
<?php endforeach; ?>
</select>
<p class="description"><?php _e('Where should this code be inserted?', 'wptag'); ?></p>
</td>
</tr>
<tr>
<th scope="row">
<label for="category"><?php _e('Category', 'wptag'); ?></label>
</th>
<td>
<select id="category" name="category">
<?php foreach ($categories as $key => $label) : ?>
<option value="<?php echo esc_attr($key); ?>" <?php selected($snippet['category'] ?? 'custom', $key); ?>>
<?php echo esc_html($label); ?>
</option>
<?php endforeach; ?>
</select>
</td>
</tr>
<tr>
<th scope="row">
<label for="priority"><?php _e('Priority', 'wptag'); ?></label>
</th>
<td>
<input type="number" id="priority" name="priority" value="<?php echo esc_attr($snippet['priority'] ?? 10); ?>" min="1" max="999">
<p class="description"><?php _e('Lower numbers = higher priority. Default is 10.', 'wptag'); ?></p>
</td>
</tr>
<tr>
<th scope="row">
<label for="device_type"><?php _e('Device Type', 'wptag'); ?></label>
</th>
<td>
<select id="device_type" name="device_type">
<option value="all" <?php selected($snippet['device_type'] ?? 'all', 'all'); ?>><?php _e('All Devices', 'wptag'); ?></option>
<option value="desktop" <?php selected($snippet['device_type'] ?? '', 'desktop'); ?>><?php _e('Desktop Only', 'wptag'); ?></option>
<option value="mobile" <?php selected($snippet['device_type'] ?? '', 'mobile'); ?>><?php _e('Mobile Only', 'wptag'); ?></option>
<option value="tablet" <?php selected($snippet['device_type'] ?? '', 'tablet'); ?>><?php _e('Tablet Only', 'wptag'); ?></option>
</select>
</td>
</tr>
<tr>
<th scope="row">
<label for="load_method"><?php _e('Load Method', 'wptag'); ?></label>
</th>
<td>
<select id="load_method" name="load_method">
<option value="normal" <?php selected($snippet['load_method'] ?? 'normal', 'normal'); ?>><?php _e('Normal', 'wptag'); ?></option>
<option value="async" <?php selected($snippet['load_method'] ?? '', 'async'); ?>><?php _e('Async', 'wptag'); ?></option>
<option value="defer" <?php selected($snippet['load_method'] ?? '', 'defer'); ?>><?php _e('Defer', 'wptag'); ?></option>
</select>
<p class="description"><?php _e('Only applies to JavaScript code', 'wptag'); ?></p>
</td>
</tr>
<tr>
<th scope="row">
<?php _e('Status', 'wptag'); ?>
</th>
<td>
<label>
<input type="checkbox" name="status" value="1" <?php checked($snippet['status'] ?? 1, 1); ?>>
<?php _e('Active', 'wptag'); ?>
</label>
<p class="description"><?php _e('Only active snippets will be displayed on your site', 'wptag'); ?></p>
</td>
</tr>
<tr>
<th scope="row">
<?php _e('Conditions', 'wptag'); ?>
</th>
<td>
<div class="wptag-conditions-builder">
<p><?php _e('Add conditions to control when this snippet should be displayed', 'wptag'); ?></p>
<div class="wptag-condition-group">
<div class="wptag-conditions-list">
<?php if (!empty($snippet['conditions']['rules'])) : ?>
<?php foreach ($snippet['conditions']['rules'] as $rule) : ?>
<div class="wptag-condition-row">
<select name="conditions[rules][][type]" class="condition-type">
<option value="">Select Type</option>
<option value="page_type" <?php selected($rule['type'], 'page_type'); ?>>Page Type</option>
<option value="user_status" <?php selected($rule['type'], 'user_status'); ?>>User Status</option>
<option value="device_type" <?php selected($rule['type'], 'device_type'); ?>>Device Type</option>
</select>
<select name="conditions[rules][][operator]" class="condition-operator">
<option value="equals" <?php selected($rule['operator'] ?? 'equals', 'equals'); ?>>Equals</option>
<option value="not_equals" <?php selected($rule['operator'] ?? '', 'not_equals'); ?>>Not Equals</option>
</select>
<input type="text" name="conditions[rules][][value]" class="condition-value" value="<?php echo esc_attr($rule['value'] ?? ''); ?>" placeholder="Value">
<button type="button" class="button wptag-remove-condition">Remove</button>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<button type="button" class="button wptag-add-condition">Add Condition</button>
</div>
</div>
</td>
</tr>
</table>
<p class="submit">
<button type="submit" class="button button-primary">
<?php echo $is_edit ? __('Update Snippet', 'wptag') : __('Create Snippet', 'wptag'); ?>
</button>
</p>
</form>
</div>
</div>

149
admin/partials/snippets.php Normal file
View file

@ -0,0 +1,149 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
$action = $_GET['action'] ?? 'list';
$snippet_id = intval($_GET['snippet_id'] ?? 0);
if ($action === 'new' || ($action === 'edit' && $snippet_id)) {
include 'snippet-form.php';
return;
}
$current_page = intval($_GET['paged'] ?? 1);
$per_page = 20;
$args = [
'search' => sanitize_text_field($_GET['search'] ?? ''),
'category' => sanitize_key($_GET['filter_category'] ?? ''),
'position' => sanitize_key($_GET['filter_position'] ?? ''),
'status' => isset($_GET['filter_status']) ? intval($_GET['filter_status']) : null,
'per_page' => $per_page,
'page' => $current_page
];
$snippets = $this->snippet_manager->get_snippets($args);
$categories = $this->snippet_manager->get_categories();
$positions = $this->snippet_manager->get_positions();
?>
<div class="wrap wptag-admin-wrap">
<div class="wptag-header">
<h1>
<?php _e('Code Snippets', 'wptag'); ?>
<a href="<?php echo admin_url('admin.php?page=wptag-snippets&action=new'); ?>" class="page-title-action">
<?php _e('Add New', 'wptag'); ?>
</a>
</h1>
</div>
<div class="wptag-content">
<div class="wptag-filters">
<div class="wptag-filter-item">
<label for="filter-search"><?php _e('Search', 'wptag'); ?></label>
<input type="text" id="filter-search" class="wptag-filter" value="<?php echo esc_attr($args['search']); ?>" placeholder="<?php esc_attr_e('Search snippets...', 'wptag'); ?>">
</div>
<div class="wptag-filter-item">
<label for="filter-category"><?php _e('Category', 'wptag'); ?></label>
<select id="filter-category" class="wptag-filter">
<option value=""><?php _e('All Categories', 'wptag'); ?></option>
<?php foreach ($categories as $key => $label) : ?>
<option value="<?php echo esc_attr($key); ?>" <?php selected($args['category'], $key); ?>>
<?php echo esc_html($label); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="wptag-filter-item">
<label for="filter-position"><?php _e('Position', 'wptag'); ?></label>
<select id="filter-position" class="wptag-filter">
<option value=""><?php _e('All Positions', 'wptag'); ?></option>
<?php foreach ($positions as $key => $label) : ?>
<option value="<?php echo esc_attr($key); ?>" <?php selected($args['position'], $key); ?>>
<?php echo esc_html($label); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="wptag-filter-item">
<label for="filter-status"><?php _e('Status', 'wptag'); ?></label>
<select id="filter-status" class="wptag-filter">
<option value=""><?php _e('All Status', 'wptag'); ?></option>
<option value="1" <?php selected($args['status'], 1); ?>><?php _e('Active', 'wptag'); ?></option>
<option value="0" <?php selected($args['status'], 0); ?>><?php _e('Inactive', 'wptag'); ?></option>
</select>
</div>
</div>
<?php if (empty($snippets)) : ?>
<div class="wptag-empty-state">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
<line x1="16" y1="13" x2="8" y2="13"/>
<line x1="16" y1="17" x2="8" y2="17"/>
<polyline points="10 9 9 9 8 9"/>
</svg>
<h3><?php _e('No snippets found', 'wptag'); ?></h3>
<p><?php _e('Get started by creating your first code snippet.', 'wptag'); ?></p>
<a href="<?php echo admin_url('admin.php?page=wptag-snippets&action=new'); ?>" class="button button-primary">
<?php _e('Create Snippet', 'wptag'); ?>
</a>
</div>
<?php else : ?>
<table class="wptag-table">
<thead>
<tr>
<th><?php _e('Name', 'wptag'); ?></th>
<th><?php _e('Position', 'wptag'); ?></th>
<th><?php _e('Category', 'wptag'); ?></th>
<th><?php _e('Priority', 'wptag'); ?></th>
<th><?php _e('Status', 'wptag'); ?></th>
<th><?php _e('Actions', 'wptag'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($snippets as $snippet) : ?>
<tr>
<td>
<strong>
<a href="<?php echo admin_url('admin.php?page=wptag-snippets&action=edit&snippet_id=' . $snippet['id']); ?>">
<?php echo esc_html($snippet['name']); ?>
</a>
</strong>
<?php if (!empty($snippet['description'])) : ?>
<br><small><?php echo esc_html($snippet['description']); ?></small>
<?php endif; ?>
</td>
<td><?php echo esc_html($positions[$snippet['position']] ?? $snippet['position']); ?></td>
<td><?php echo esc_html($categories[$snippet['category']] ?? $snippet['category']); ?></td>
<td><?php echo esc_html($snippet['priority']); ?></td>
<td>
<span class="wptag-status-badge <?php echo $snippet['status'] ? 'active' : 'inactive'; ?>">
<?php echo $snippet['status'] ? __('Active', 'wptag') : __('Inactive', 'wptag'); ?>
</span>
</td>
<td>
<div class="wptag-actions">
<a href="<?php echo admin_url('admin.php?page=wptag-snippets&action=edit&snippet_id=' . $snippet['id']); ?>" class="wptag-action-link">
<?php _e('Edit', 'wptag'); ?>
</a>
<a href="#" class="wptag-action-link wptag-toggle-status" data-snippet-id="<?php echo $snippet['id']; ?>">
<?php echo $snippet['status'] ? __('Disable', 'wptag') : __('Enable', 'wptag'); ?>
</a>
<a href="#" class="wptag-action-link delete wptag-delete-snippet" data-snippet-id="<?php echo $snippet['id']; ?>">
<?php _e('Delete', 'wptag'); ?>
</a>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</div>

View file

@ -0,0 +1,119 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
$selected_category = sanitize_key($_GET['category'] ?? '');
$templates = $this->template_manager->get_templates($selected_category);
$categories = $this->template_manager->get_categories();
$templates_by_category = [];
foreach ($templates as $template) {
$cat = $template['service_category'];
if (!isset($templates_by_category[$cat])) {
$templates_by_category[$cat] = [];
}
$templates_by_category[$cat][] = $template;
}
?>
<div class="wrap wptag-admin-wrap">
<div class="wptag-header">
<h1><?php _e('Service Templates', 'wptag'); ?></h1>
</div>
<div class="wptag-content">
<div class="wptag-notice info">
<span class="dashicons dashicons-info"></span>
<p><?php _e('Service templates help you quickly add popular services to your site. Simply select a template, enter your configuration details, and a snippet will be created automatically.', 'wptag'); ?></p>
</div>
<div class="wptag-tabs">
<a href="<?php echo admin_url('admin.php?page=wptag-templates'); ?>" class="wptag-tab <?php echo empty($selected_category) ? 'active' : ''; ?>">
<?php _e('All Templates', 'wptag'); ?>
</a>
<?php foreach ($categories as $key => $label) : ?>
<a href="<?php echo admin_url('admin.php?page=wptag-templates&category=' . $key); ?>"
class="wptag-tab <?php echo $selected_category === $key ? 'active' : ''; ?>">
<?php echo esc_html($label); ?>
</a>
<?php endforeach; ?>
</div>
<?php if (empty($templates)) : ?>
<div class="wptag-empty-state">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
<line x1="9" y1="9" x2="15" y2="9"/>
<line x1="9" y1="12" x2="15" y2="12"/>
<line x1="9" y1="15" x2="13" y2="15"/>
</svg>
<h3><?php _e('No templates found', 'wptag'); ?></h3>
<p><?php _e('No templates available in this category.', 'wptag'); ?></p>
</div>
<?php else : ?>
<?php foreach ($templates_by_category as $category => $cat_templates) : ?>
<?php if ($selected_category && $selected_category !== $category) continue; ?>
<h2><?php echo esc_html($categories[$category] ?? $category); ?></h2>
<div class="wptag-templates-grid">
<?php foreach ($cat_templates as $template) : ?>
<div class="wptag-template-card">
<h3><?php echo esc_html($template['service_name']); ?></h3>
<p><?php echo esc_html(get_template_description($template['service_type'])); ?></p>
<div class="template-meta">
<span class="template-version">v<?php echo esc_html($template['version']); ?></span>
<span class="template-position"><?php echo esc_html(ucfirst($template['default_position'])); ?></span>
</div>
<button type="button"
class="button button-primary wptag-template-use"
data-service-type="<?php echo esc_attr($template['service_type']); ?>">
<?php _e('Use This Template', 'wptag'); ?>
</button>
</div>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
<style>
.template-meta {
display: flex;
justify-content: space-between;
margin-bottom: 15px;
font-size: 12px;
color: #666;
}
.template-version,
.template-position {
background: #f0f0f1;
padding: 2px 8px;
border-radius: 3px;
}
</style>
<?php
// Helper method to get template descriptions
function get_template_description($service_type) {
$descriptions = [
'google_analytics_4' => __('Track website traffic and user behavior with Google Analytics 4', 'wptag'),
'google_analytics_universal' => __('Track website traffic with Universal Analytics (legacy)', 'wptag'),
'facebook_pixel' => __('Track conversions and build audiences for Facebook ads', 'wptag'),
'google_ads' => __('Track conversions for Google Ads campaigns', 'wptag'),
'google_search_console' => __('Verify site ownership for Google Search Console', 'wptag'),
'baidu_tongji' => __('Track website traffic with Baidu Analytics', 'wptag'),
'cnzz' => __('Track website traffic with CNZZ Analytics', 'wptag'),
'51la' => __('Track website traffic with 51.la Analytics', 'wptag'),
'baidu_push' => __('Submit URLs to Baidu for faster indexing', 'wptag'),
'toutiao_pixel' => __('Track conversions for Toutiao/TikTok ads', 'wptag'),
];
return $descriptions[$service_type] ?? __('Configure and add this service to your site', 'wptag');
}
?>

374
assets/admin.css Normal file
View file

@ -0,0 +1,374 @@
.wptag-admin-wrap {
margin-top: 20px;
}
.wptag-header {
background: #fff;
border: 1px solid #ccd0d4;
border-bottom: 0;
padding: 20px;
margin-bottom: 0;
}
.wptag-header h1 {
margin: 0;
font-size: 24px;
font-weight: 400;
line-height: 1.3;
}
.wptag-header .page-title-action {
margin-left: 10px;
}
.wptag-content {
background: #fff;
border: 1px solid #ccd0d4;
padding: 20px;
}
.wptag-stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.wptag-stat-card {
background: #f8f9fa;
border: 1px solid #e2e4e7;
border-radius: 4px;
padding: 20px;
text-align: center;
}
.wptag-stat-card h3 {
margin: 0 0 10px;
font-size: 14px;
color: #666;
font-weight: 400;
}
.wptag-stat-card .stat-value {
font-size: 32px;
font-weight: 600;
color: #2271b1;
}
.wptag-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
.wptag-table th,
.wptag-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #e2e4e7;
}
.wptag-table th {
background: #f8f9fa;
font-weight: 600;
color: #2c3338;
}
.wptag-table tbody tr:hover {
background: #f6f7f7;
}
.wptag-status-badge {
display: inline-block;
padding: 3px 8px;
border-radius: 3px;
font-size: 12px;
font-weight: 500;
}
.wptag-status-badge.active {
background: #d4f4dd;
color: #00a32a;
}
.wptag-status-badge.inactive {
background: #f5e6e6;
color: #d63638;
}
.wptag-actions {
display: flex;
gap: 10px;
}
.wptag-action-link {
color: #2271b1;
text-decoration: none;
font-size: 13px;
}
.wptag-action-link:hover {
color: #135e96;
text-decoration: underline;
}
.wptag-action-link.delete {
color: #d63638;
}
.wptag-action-link.delete:hover {
color: #a02222;
}
.wptag-form-table {
width: 100%;
max-width: 800px;
}
.wptag-form-table th {
width: 200px;
padding: 20px 10px 20px 0;
vertical-align: top;
text-align: left;
font-weight: 600;
}
.wptag-form-table td {
padding: 15px 10px;
}
.wptag-form-table input[type="text"],
.wptag-form-table input[type="number"],
.wptag-form-table select,
.wptag-form-table textarea {
width: 100%;
max-width: 400px;
}
.wptag-code-editor {
width: 100%;
min-height: 300px;
font-family: Consolas, Monaco, monospace;
font-size: 13px;
}
.wptag-conditions-builder {
background: #f8f9fa;
border: 1px solid #e2e4e7;
border-radius: 4px;
padding: 20px;
margin-top: 10px;
}
.wptag-condition-group {
background: #fff;
border: 1px solid #e2e4e7;
border-radius: 4px;
padding: 15px;
margin-bottom: 15px;
}
.wptag-condition-row {
display: flex;
gap: 10px;
align-items: center;
margin-bottom: 10px;
}
.wptag-condition-row select,
.wptag-condition-row input {
flex: 1;
min-width: 0;
}
.wptag-templates-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
margin-top: 20px;
}
.wptag-template-card {
background: #fff;
border: 1px solid #e2e4e7;
border-radius: 4px;
padding: 20px;
transition: box-shadow 0.2s;
}
.wptag-template-card:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.wptag-template-card h3 {
margin: 0 0 10px;
font-size: 16px;
color: #2c3338;
}
.wptag-template-card p {
color: #666;
margin: 0 0 15px;
font-size: 14px;
}
.wptag-template-card .button {
width: 100%;
text-align: center;
}
.wptag-modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
z-index: 100000;
}
.wptag-modal-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #fff;
border-radius: 4px;
max-width: 600px;
width: 90%;
max-height: 90vh;
overflow: auto;
}
.wptag-modal-header {
padding: 20px;
border-bottom: 1px solid #e2e4e7;
}
.wptag-modal-header h2 {
margin: 0;
font-size: 20px;
}
.wptag-modal-body {
padding: 20px;
}
.wptag-modal-footer {
padding: 20px;
border-top: 1px solid #e2e4e7;
text-align: right;
}
.wptag-filters {
display: flex;
gap: 15px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.wptag-filter-item {
display: flex;
flex-direction: column;
gap: 5px;
}
.wptag-filter-item label {
font-size: 13px;
font-weight: 600;
color: #2c3338;
}
.wptag-empty-state {
text-align: center;
padding: 60px 20px;
color: #666;
}
.wptag-empty-state svg {
width: 64px;
height: 64px;
margin-bottom: 20px;
opacity: 0.3;
}
.wptag-empty-state h3 {
font-size: 18px;
font-weight: 400;
margin: 0 0 10px;
}
.wptag-empty-state p {
margin: 0 0 20px;
}
.wptag-tabs {
display: flex;
border-bottom: 1px solid #e2e4e7;
margin: -20px -20px 20px;
padding: 0 20px;
}
.wptag-tab {
padding: 15px 20px;
border-bottom: 2px solid transparent;
text-decoration: none;
color: #2c3338;
font-weight: 500;
transition: all 0.2s;
}
.wptag-tab:hover {
color: #2271b1;
}
.wptag-tab.active {
color: #2271b1;
border-bottom-color: #2271b1;
}
.wptag-notice {
padding: 12px 20px;
margin: 20px 0;
border-radius: 4px;
display: flex;
align-items: center;
gap: 10px;
}
.wptag-notice.info {
background: #e5f5fa;
color: #0073aa;
}
.wptag-notice.success {
background: #d4f4dd;
color: #00a32a;
}
.wptag-notice.warning {
background: #fcf9e8;
color: #996800;
}
.wptag-notice.error {
background: #f5e6e6;
color: #d63638;
}
@media screen and (max-width: 782px) {
.wptag-form-table th {
width: auto;
display: block;
padding-bottom: 5px;
}
.wptag-filters {
flex-direction: column;
}
.wptag-condition-row {
flex-direction: column;
}
}

322
assets/admin.js Normal file
View file

@ -0,0 +1,322 @@
(function($) {
'use strict';
const WPTagAdmin = {
init: function() {
this.bindEvents();
this.initCodeEditor();
this.initConditionsBuilder();
},
bindEvents: function() {
$(document).on('click', '.wptag-toggle-status', this.toggleStatus);
$(document).on('click', '.wptag-delete-snippet', this.deleteSnippet);
$(document).on('change', '.wptag-filter', this.filterSnippets);
$(document).on('click', '.wptag-template-use', this.useTemplate);
$(document).on('submit', '.wptag-snippet-form', this.validateForm);
$(document).on('click', '.wptag-add-condition', this.addCondition);
$(document).on('click', '.wptag-remove-condition', this.removeCondition);
$(document).on('change', '#code_type', this.updateCodeEditor);
},
toggleStatus: function(e) {
e.preventDefault();
const $link = $(this);
const snippetId = $link.data('snippet-id');
$.ajax({
url: wptagAdmin.ajaxUrl,
type: 'POST',
data: {
action: 'wptag_toggle_snippet',
snippet_id: snippetId,
nonce: wptagAdmin.nonce
},
success: function(response) {
if (response.success) {
location.reload();
} else {
alert(response.data.message || wptagAdmin.strings.error);
}
},
error: function() {
alert(wptagAdmin.strings.error);
}
});
},
deleteSnippet: function(e) {
e.preventDefault();
if (!confirm(wptagAdmin.strings.confirmDelete)) {
return;
}
const $link = $(this);
const snippetId = $link.data('snippet-id');
$.ajax({
url: wptagAdmin.ajaxUrl,
type: 'POST',
data: {
action: 'wptag_delete_snippet',
snippet_id: snippetId,
nonce: wptagAdmin.nonce
},
success: function(response) {
if (response.success) {
$link.closest('tr').fadeOut(400, function() {
$(this).remove();
});
} else {
alert(response.data.message || wptagAdmin.strings.error);
}
},
error: function() {
alert(wptagAdmin.strings.error);
}
});
},
filterSnippets: function() {
const filters = {
search: $('#filter-search').val(),
category: $('#filter-category').val(),
position: $('#filter-position').val(),
status: $('#filter-status').val()
};
const params = new URLSearchParams(filters);
params.delete('page');
Object.keys(filters).forEach(key => {
if (!filters[key]) {
params.delete(key);
}
});
window.location.href = window.location.pathname + '?page=wptag-snippets&' + params.toString();
},
initCodeEditor: function() {
const $codeTextarea = $('#snippet-code');
if ($codeTextarea.length && typeof wp !== 'undefined' && wp.codeEditor) {
const editorSettings = wp.codeEditor.defaultSettings || {};
const codeType = $('#code_type').val();
editorSettings.codemirror = {
...editorSettings.codemirror,
mode: this.getCodeMirrorMode(codeType),
lineNumbers: true,
lineWrapping: true,
indentUnit: 2,
tabSize: 2
};
this.codeEditor = wp.codeEditor.initialize($codeTextarea, editorSettings);
}
},
getCodeMirrorMode: function(codeType) {
const modes = {
'html': 'htmlmixed',
'javascript': 'javascript',
'css': 'css'
};
return modes[codeType] || 'htmlmixed';
},
updateCodeEditor: function() {
const codeType = $(this).val();
if (WPTagAdmin.codeEditor && WPTagAdmin.codeEditor.codemirror) {
WPTagAdmin.codeEditor.codemirror.setOption('mode', WPTagAdmin.getCodeMirrorMode(codeType));
}
},
initConditionsBuilder: function() {
const $builder = $('.wptag-conditions-builder');
if (!$builder.length) {
return;
}
this.loadConditionTypes();
},
loadConditionTypes: function() {
$.ajax({
url: wptagAdmin.ajaxUrl,
type: 'POST',
data: {
action: 'wptag_get_condition_types',
nonce: wptagAdmin.nonce
},
success: function(response) {
if (response.success) {
WPTagAdmin.conditionTypes = response.data.types;
}
}
});
},
addCondition: function(e) {
e.preventDefault();
const $group = $(this).closest('.wptag-condition-group');
const $newRow = WPTagAdmin.createConditionRow();
$group.find('.wptag-conditions-list').append($newRow);
},
removeCondition: function(e) {
e.preventDefault();
$(this).closest('.wptag-condition-row').fadeOut(300, function() {
$(this).remove();
});
},
createConditionRow: function() {
const html = `
<div class="wptag-condition-row">
<select name="conditions[rules][][type]" class="condition-type">
<option value="">Select Type</option>
<option value="page_type">Page Type</option>
<option value="user_status">User Status</option>
<option value="device_type">Device Type</option>
</select>
<select name="conditions[rules][][operator]" class="condition-operator">
<option value="equals">Equals</option>
<option value="not_equals">Not Equals</option>
</select>
<input type="text" name="conditions[rules][][value]" class="condition-value" placeholder="Value">
<button type="button" class="button wptag-remove-condition">Remove</button>
</div>
`;
return $(html);
},
useTemplate: function(e) {
e.preventDefault();
const $button = $(this);
const serviceType = $button.data('service-type');
$.ajax({
url: wptagAdmin.ajaxUrl,
type: 'POST',
data: {
action: 'wptag_get_template',
service_type: serviceType,
nonce: wptagAdmin.nonce
},
success: function(response) {
if (response.success) {
WPTagAdmin.showTemplateModal(response.data.template);
} else {
alert(response.data.message || wptagAdmin.strings.error);
}
}
});
},
showTemplateModal: function(template) {
const fields = template.config_fields.map(field => {
return `
<div class="wptag-form-field">
<label for="${field.name}">${field.label}</label>
<input type="${field.type || 'text'}"
id="${field.name}"
name="${field.name}"
${field.required ? 'required' : ''}>
</div>
`;
}).join('');
const modalHtml = `
<div class="wptag-modal" id="template-config-modal">
<div class="wptag-modal-content">
<div class="wptag-modal-header">
<h2>${template.service_name} Configuration</h2>
</div>
<form id="template-config-form">
<div class="wptag-modal-body">
${fields}
</div>
<div class="wptag-modal-footer">
<button type="button" class="button" onclick="WPTagAdmin.closeModal()">Cancel</button>
<button type="submit" class="button button-primary">Create Snippet</button>
</div>
</form>
</div>
</div>
`;
$('body').append(modalHtml);
$('#template-config-modal').fadeIn();
$('#template-config-form').on('submit', function(e) {
e.preventDefault();
WPTagAdmin.processTemplate(template.service_type, $(this).serialize());
});
},
processTemplate: function(serviceType, formData) {
$.ajax({
url: wptagAdmin.ajaxUrl,
type: 'POST',
data: {
action: 'wptag_process_template',
service_type: serviceType,
config: formData,
nonce: wptagAdmin.nonce
},
success: function(response) {
if (response.success) {
window.location.href = `admin.php?page=wptag-snippets&action=edit&snippet_id=${response.data.snippet_id}`;
} else {
alert(response.data.message || wptagAdmin.strings.error);
}
}
});
},
closeModal: function() {
$('.wptag-modal').fadeOut(300, function() {
$(this).remove();
});
},
validateForm: function(e) {
const $form = $(this);
const name = $form.find('#snippet-name').val();
const code = WPTagAdmin.codeEditor ?
WPTagAdmin.codeEditor.codemirror.getValue() :
$form.find('#snippet-code').val();
if (!name.trim()) {
e.preventDefault();
alert('Please enter a snippet name');
return false;
}
if (!code.trim()) {
e.preventDefault();
alert('Please enter some code');
return false;
}
return true;
}
};
$(document).ready(function() {
WPTagAdmin.init();
});
})(jQuery);

View file

@ -0,0 +1,160 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPTag_Cache_Manager {
private $cache_group = 'wptag';
private $cache_enabled;
private $ttl;
public function __construct() {
$this->cache_enabled = !defined('WPTAG_DISABLE_CACHE') || !WPTAG_DISABLE_CACHE;
$this->ttl = defined('WPTAG_CACHE_TTL') ? WPTAG_CACHE_TTL : 3600;
}
public function get($key) {
if (!$this->cache_enabled) {
return false;
}
return wp_cache_get($key, $this->cache_group);
}
public function set($key, $value, $ttl = null) {
if (!$this->cache_enabled) {
return false;
}
$ttl = $ttl ?? $this->ttl;
return wp_cache_set($key, $value, $this->cache_group, $ttl);
}
public function delete($key) {
return wp_cache_delete($key, $this->cache_group);
}
public function flush() {
wp_cache_flush();
}
public function clear_snippet_cache($snippet_id = null) {
if ($snippet_id) {
$this->delete('snippet_' . $snippet_id);
}
$this->delete('active_snippets');
$this->clear_output_cache();
}
public function clear_output_cache() {
$positions = ['head', 'footer', 'before_content', 'after_content'];
foreach ($positions as $position) {
$this->delete_by_prefix('output_' . $position);
}
}
public function clear_condition_cache() {
$this->delete_by_prefix('condition_');
}
private function delete_by_prefix($prefix) {
global $wp_object_cache;
if (method_exists($wp_object_cache, 'delete_by_group')) {
$wp_object_cache->delete_by_group($this->cache_group);
} else {
$this->delete($prefix);
}
}
public function warm_cache() {
if (!$this->cache_enabled) {
return;
}
global $wpdb;
$table = $wpdb->prefix . 'wptag_snippets';
$active_snippets = $wpdb->get_results(
"SELECT * FROM $table WHERE status = 1 ORDER BY priority ASC",
ARRAY_A
);
$by_position = [];
foreach ($active_snippets as $snippet) {
$position = $snippet['position'];
if (!isset($by_position[$position])) {
$by_position[$position] = [];
}
$by_position[$position][] = $snippet;
}
foreach ($by_position as $position => $snippets) {
$this->set('snippets_' . $position, $snippets);
}
$this->set('active_snippets', $active_snippets);
}
public function get_cache_stats() {
global $wp_object_cache;
$stats = [
'enabled' => $this->cache_enabled,
'ttl' => $this->ttl,
'hits' => 0,
'misses' => 0,
'size' => 0
];
if (method_exists($wp_object_cache, 'stats')) {
$cache_stats = $wp_object_cache->stats();
$stats['hits'] = $cache_stats['hits'] ?? 0;
$stats['misses'] = $cache_stats['misses'] ?? 0;
}
return $stats;
}
public function schedule_cleanup() {
if (!wp_next_scheduled('wptag_cache_cleanup')) {
wp_schedule_event(time(), 'daily', 'wptag_cache_cleanup');
}
add_action('wptag_cache_cleanup', [$this, 'cleanup_expired']);
}
public function cleanup_expired() {
global $wpdb;
$logs_table = $wpdb->prefix . 'wptag_logs';
$thirty_days_ago = date('Y-m-d H:i:s', strtotime('-30 days'));
$wpdb->query($wpdb->prepare(
"DELETE FROM $logs_table WHERE created_at < %s",
$thirty_days_ago
));
$this->clear_output_cache();
}
public function invalidate_on_save($post_id) {
if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) {
return;
}
$this->clear_output_cache();
}
public function init_hooks() {
add_action('save_post', [$this, 'invalidate_on_save']);
add_action('switch_theme', [$this, 'flush']);
add_action('activated_plugin', [$this, 'flush']);
add_action('deactivated_plugin', [$this, 'flush']);
$this->schedule_cleanup();
}
}

View file

@ -0,0 +1,311 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPTag_Condition_Engine {
private $context_cache = [];
public function evaluate_conditions($conditions, $context = []) {
if (empty($conditions) || !is_array($conditions)) {
return true;
}
$cache_key = md5(json_encode($conditions) . json_encode($context));
if (isset($this->context_cache[$cache_key])) {
return $this->context_cache[$cache_key];
}
$result = $this->process_condition_group($conditions, $context);
$this->context_cache[$cache_key] = $result;
return $result;
}
private function process_condition_group($conditions, $context) {
$logic = $conditions['logic'] ?? 'AND';
$rules = $conditions['rules'] ?? [];
if (empty($rules)) {
return true;
}
$results = [];
foreach ($rules as $rule) {
if (isset($rule['rules'])) {
$results[] = $this->process_condition_group($rule, $context);
} else {
$results[] = $this->evaluate_single_condition($rule, $context);
}
}
if ($logic === 'OR') {
return in_array(true, $results, true);
} else {
return !in_array(false, $results, true);
}
}
private function evaluate_single_condition($rule, $context) {
$type = $rule['type'] ?? '';
$operator = $rule['operator'] ?? 'equals';
$value = $rule['value'] ?? '';
switch ($type) {
case 'page_type':
return $this->check_page_type($value, $operator);
case 'user_status':
return $this->check_user_status($value, $operator);
case 'user_role':
return $this->check_user_role($value, $operator);
case 'device_type':
return $this->check_device_type($value, $operator);
case 'post_id':
return $this->check_post_id($value, $operator);
case 'category':
return $this->check_category($value, $operator);
case 'tag':
return $this->check_tag($value, $operator);
case 'url':
return $this->check_url($value, $operator);
case 'date_range':
return $this->check_date_range($value, $operator);
case 'time':
return $this->check_time($value, $operator);
case 'day_of_week':
return $this->check_day_of_week($value, $operator);
default:
return apply_filters('wptag_custom_condition', true, $type, $value, $operator, $context);
}
}
private function check_page_type($value, $operator) {
$page_types = [
'home' => is_home() || is_front_page(),
'single' => is_single(),
'page' => is_page(),
'archive' => is_archive(),
'category' => is_category(),
'tag' => is_tag(),
'search' => is_search(),
'404' => is_404(),
'author' => is_author(),
'date' => is_date()
];
$is_type = $page_types[$value] ?? false;
return $operator === 'not_equals' ? !$is_type : $is_type;
}
private function check_user_status($value, $operator) {
$is_logged_in = is_user_logged_in();
if ($value === 'logged_in') {
return $operator === 'not_equals' ? !$is_logged_in : $is_logged_in;
} else {
return $operator === 'not_equals' ? $is_logged_in : !$is_logged_in;
}
}
private function check_user_role($value, $operator) {
if (!is_user_logged_in()) {
return $operator === 'not_equals';
}
$user = wp_get_current_user();
$has_role = in_array($value, $user->roles);
return $operator === 'not_equals' ? !$has_role : $has_role;
}
private function check_device_type($value, $operator) {
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$is_mobile = wp_is_mobile();
$device_checks = [
'mobile' => $is_mobile && !$this->is_tablet($user_agent),
'tablet' => $this->is_tablet($user_agent),
'desktop' => !$is_mobile
];
$is_device = $device_checks[$value] ?? false;
return $operator === 'not_equals' ? !$is_device : $is_device;
}
private function is_tablet($user_agent) {
$tablet_patterns = '/iPad|Android.*Tablet|Tablet.*Android|Kindle|Silk|Galaxy Tab/i';
return preg_match($tablet_patterns, $user_agent);
}
private function check_post_id($value, $operator) {
$current_id = get_the_ID();
$ids = array_map('intval', explode(',', $value));
switch ($operator) {
case 'equals':
return in_array($current_id, $ids);
case 'not_equals':
return !in_array($current_id, $ids);
case 'greater_than':
return $current_id > $ids[0];
case 'less_than':
return $current_id < $ids[0];
default:
return false;
}
}
private function check_category($value, $operator) {
if (!is_single() && !is_category()) {
return $operator === 'not_equals';
}
$categories = explode(',', $value);
$has_category = false;
if (is_single()) {
foreach ($categories as $cat) {
if (has_category($cat)) {
$has_category = true;
break;
}
}
} elseif (is_category()) {
$current_cat = get_queried_object();
$has_category = in_array($current_cat->slug, $categories) ||
in_array($current_cat->term_id, $categories);
}
return $operator === 'not_equals' ? !$has_category : $has_category;
}
private function check_tag($value, $operator) {
if (!is_single() && !is_tag()) {
return $operator === 'not_equals';
}
$tags = explode(',', $value);
$has_tag = false;
if (is_single()) {
foreach ($tags as $tag) {
if (has_tag($tag)) {
$has_tag = true;
break;
}
}
} elseif (is_tag()) {
$current_tag = get_queried_object();
$has_tag = in_array($current_tag->slug, $tags) ||
in_array($current_tag->term_id, $tags);
}
return $operator === 'not_equals' ? !$has_tag : $has_tag;
}
private function check_url($value, $operator) {
$current_url = $_SERVER['REQUEST_URI'] ?? '';
switch ($operator) {
case 'contains':
return strpos($current_url, $value) !== false;
case 'not_contains':
return strpos($current_url, $value) === false;
case 'equals':
return $current_url === $value;
case 'not_equals':
return $current_url !== $value;
case 'starts_with':
return strpos($current_url, $value) === 0;
case 'ends_with':
return substr($current_url, -strlen($value)) === $value;
default:
return false;
}
}
private function check_date_range($value, $operator) {
$current_time = current_time('timestamp');
$dates = explode('|', $value);
if (count($dates) !== 2) {
return false;
}
$start_date = strtotime($dates[0]);
$end_date = strtotime($dates[1] . ' 23:59:59');
$in_range = $current_time >= $start_date && $current_time <= $end_date;
return $operator === 'not_in' ? !$in_range : $in_range;
}
private function check_time($value, $operator) {
$current_time = current_time('H:i');
$times = explode('|', $value);
if (count($times) !== 2) {
return false;
}
$in_range = $current_time >= $times[0] && $current_time <= $times[1];
return $operator === 'not_in' ? !$in_range : $in_range;
}
private function check_day_of_week($value, $operator) {
$current_day = strtolower(current_time('l'));
$days = array_map('strtolower', explode(',', $value));
$is_day = in_array($current_day, $days);
return $operator === 'not_in' ? !$is_day : $is_day;
}
public function get_condition_types() {
return [
'page_type' => [
'label' => __('Page Type', 'wptag'),
'values' => [
'home' => __('Home Page', 'wptag'),
'single' => __('Single Post', 'wptag'),
'page' => __('Page', 'wptag'),
'archive' => __('Archive', 'wptag'),
'category' => __('Category', 'wptag'),
'tag' => __('Tag', 'wptag'),
'search' => __('Search', 'wptag'),
'404' => __('404 Page', 'wptag')
]
],
'user_status' => [
'label' => __('User Status', 'wptag'),
'values' => [
'logged_in' => __('Logged In', 'wptag'),
'logged_out' => __('Logged Out', 'wptag')
]
],
'device_type' => [
'label' => __('Device Type', 'wptag'),
'values' => [
'mobile' => __('Mobile', 'wptag'),
'tablet' => __('Tablet', 'wptag'),
'desktop' => __('Desktop', 'wptag')
]
]
];
}
}

View file

@ -0,0 +1,267 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPTag_Core {
private static $instance = null;
private $snippet_manager;
private $condition_engine;
private $output_controller;
private $template_manager;
private $cache_manager;
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->load_dependencies();
$this->init();
}
private function load_dependencies() {
require_once WPTAG_PLUGIN_DIR . 'includes/class-wptag-snippet-manager.php';
require_once WPTAG_PLUGIN_DIR . 'includes/class-wptag-condition-engine.php';
require_once WPTAG_PLUGIN_DIR . 'includes/class-wptag-output-controller.php';
require_once WPTAG_PLUGIN_DIR . 'includes/class-wptag-template-manager.php';
require_once WPTAG_PLUGIN_DIR . 'includes/class-wptag-cache-manager.php';
if (is_admin()) {
require_once WPTAG_PLUGIN_DIR . 'admin/class-wptag-admin-controller.php';
require_once WPTAG_PLUGIN_DIR . 'admin/class-wptag-ajax-handler.php';
require_once WPTAG_PLUGIN_DIR . 'admin/class-wptag-admin-interface.php';
}
}
private function init() {
$this->snippet_manager = new WPTag_Snippet_Manager();
$this->condition_engine = new WPTag_Condition_Engine();
$this->template_manager = new WPTag_Template_Manager();
$this->cache_manager = new WPTag_Cache_Manager();
$this->output_controller = new WPTag_Output_Controller(
$this->snippet_manager,
$this->condition_engine,
$this->cache_manager
);
if (is_admin()) {
new WPTag_Admin_Controller($this->snippet_manager, $this->template_manager);
new WPTag_Ajax_Handler($this->snippet_manager, $this->template_manager);
} else {
$this->register_output_hooks();
}
add_action('init', [$this, 'check_version']);
}
private function register_output_hooks() {
add_action('wp_head', [$this->output_controller, 'render_head'], 1);
add_action('wp_footer', [$this->output_controller, 'render_footer'], 999);
add_filter('the_content', [$this->output_controller, 'filter_content'], 10);
}
public function check_version() {
$installed_version = get_option('wptag_db_version');
if ($installed_version !== WPTAG_DB_VERSION) {
self::create_tables();
update_option('wptag_db_version', WPTAG_DB_VERSION);
}
}
public static function activate() {
if (version_compare(PHP_VERSION, '8.0', '<')) {
deactivate_plugins(plugin_basename(WPTAG_PLUGIN_FILE));
wp_die('WPTAG requires PHP 8.0 or higher.');
}
if (version_compare(get_bloginfo('version'), '6.8', '<')) {
deactivate_plugins(plugin_basename(WPTAG_PLUGIN_FILE));
wp_die('WPTAG requires WordPress 6.8 or higher.');
}
self::create_tables();
self::create_default_templates();
flush_rewrite_rules();
}
public static function deactivate() {
wp_clear_scheduled_hook('wptag_cleanup_logs');
flush_rewrite_rules();
}
private static function create_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
$sql_snippets = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wptag_snippets (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
description text,
code longtext NOT NULL,
code_type varchar(50) DEFAULT 'html',
position varchar(100) NOT NULL,
category varchar(100) DEFAULT 'custom',
priority int(11) DEFAULT 10,
status tinyint(1) DEFAULT 1,
conditions longtext,
device_type varchar(50) DEFAULT 'all',
load_method varchar(50) DEFAULT 'normal',
created_by bigint(20) unsigned,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
last_modified_by bigint(20) unsigned,
PRIMARY KEY (id),
KEY idx_status_position (status, position),
KEY idx_category (category),
KEY idx_priority (priority)
) $charset_collate;";
$sql_templates = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wptag_templates (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
service_type varchar(100) NOT NULL,
service_name varchar(255) NOT NULL,
service_category varchar(100) NOT NULL,
config_fields longtext,
code_template longtext NOT NULL,
default_position varchar(100) NOT NULL,
is_active tinyint(1) DEFAULT 1,
version varchar(20) DEFAULT '1.0',
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY idx_service_type (service_type),
KEY idx_category (service_category)
) $charset_collate;";
$sql_logs = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wptag_logs (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
user_id bigint(20) unsigned NOT NULL,
action varchar(100) NOT NULL,
object_type varchar(50) NOT NULL,
object_id bigint(20) unsigned,
old_value longtext,
new_value longtext,
ip_address varchar(45),
user_agent text,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_user_action (user_id, action),
KEY idx_created_at (created_at)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql_snippets);
dbDelta($sql_templates);
dbDelta($sql_logs);
}
private static function create_default_templates() {
global $wpdb;
$table = $wpdb->prefix . 'wptag_templates';
$templates = [
[
'service_type' => 'google_analytics_4',
'service_name' => 'Google Analytics 4',
'service_category' => 'analytics',
'config_fields' => json_encode([
['name' => 'measurement_id', 'label' => 'Measurement ID', 'type' => 'text', 'required' => true]
]),
'code_template' => '<script async src="https://www.googletagmanager.com/gtag/js?id={{measurement_id}}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag("js", new Date());
gtag("config", "{{measurement_id}}");
</script>',
'default_position' => 'head'
],
[
'service_type' => 'facebook_pixel',
'service_name' => 'Facebook Pixel',
'service_category' => 'marketing',
'config_fields' => json_encode([
['name' => 'pixel_id', 'label' => 'Pixel ID', 'type' => 'text', 'required' => true]
]),
'code_template' => '<script>
!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version="2.0";
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,"script",
"https://connect.facebook.net/en_US/fbevents.js");
fbq("init", "{{pixel_id}}");
fbq("track", "PageView");
</script>
<noscript><img height="1" width="1" style="display:none"
src="https://www.facebook.com/tr?id={{pixel_id}}&ev=PageView&noscript=1"
/></noscript>',
'default_position' => 'head'
],
[
'service_type' => 'google_ads',
'service_name' => 'Google Ads Conversion',
'service_category' => 'marketing',
'config_fields' => json_encode([
['name' => 'conversion_id', 'label' => 'Conversion ID', 'type' => 'text', 'required' => true],
['name' => 'conversion_label', 'label' => 'Conversion Label', 'type' => 'text', 'required' => true]
]),
'code_template' => '<script async src="https://www.googletagmanager.com/gtag/js?id=AW-{{conversion_id}}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag("js", new Date());
gtag("config", "AW-{{conversion_id}}");
gtag("event", "conversion", {"send_to": "AW-{{conversion_id}}/{{conversion_label}}"});
</script>',
'default_position' => 'head'
],
[
'service_type' => 'google_search_console',
'service_name' => 'Google Search Console',
'service_category' => 'seo',
'config_fields' => json_encode([
['name' => 'verification_code', 'label' => 'Verification Code', 'type' => 'text', 'required' => true]
]),
'code_template' => '<meta name="google-site-verification" content="{{verification_code}}" />',
'default_position' => 'head'
],
[
'service_type' => 'baidu_tongji',
'service_name' => 'Baidu Tongji',
'service_category' => 'analytics',
'config_fields' => json_encode([
['name' => 'site_id', 'label' => 'Site ID', 'type' => 'text', 'required' => true]
]),
'code_template' => '<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?{{site_id}}";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>',
'default_position' => 'head'
]
];
foreach ($templates as $template) {
$exists = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM $table WHERE service_type = %s",
$template['service_type']
));
if (!$exists) {
$wpdb->insert($table, $template);
}
}
}
}

View file

@ -0,0 +1,178 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPTag_Output_Controller {
private $snippet_manager;
private $condition_engine;
private $cache_manager;
private $rendered_snippets = [];
public function __construct($snippet_manager, $condition_engine, $cache_manager) {
$this->snippet_manager = $snippet_manager;
$this->condition_engine = $condition_engine;
$this->cache_manager = $cache_manager;
}
public function render_head() {
$this->render_snippets('head');
}
public function render_footer() {
$this->render_snippets('footer');
}
public function filter_content($content) {
if (!in_the_loop() || !is_main_query()) {
return $content;
}
$before = $this->get_rendered_snippets('before_content');
$after = $this->get_rendered_snippets('after_content');
return $before . $content . $after;
}
private function render_snippets($position) {
echo $this->get_rendered_snippets($position);
}
private function get_rendered_snippets($position) {
$cache_key = 'wptag_output_' . $position . '_' . $this->get_cache_context();
$cached = $this->cache_manager->get($cache_key);
if ($cached !== false && !$this->is_preview_mode()) {
return $cached;
}
$snippets = $this->snippet_manager->get_active_snippets_by_position($position);
$output = '';
foreach ($snippets as $snippet) {
if ($this->should_render_snippet($snippet)) {
$output .= $this->render_single_snippet($snippet);
$this->rendered_snippets[] = $snippet['id'];
}
}
if (!empty($output)) {
$output = "\n<!-- WPTAG Start -->\n" . $output . "<!-- WPTAG End -->\n";
}
$this->cache_manager->set($cache_key, $output, 3600);
return $output;
}
private function should_render_snippet($snippet) {
if (in_array($snippet['id'], $this->rendered_snippets)) {
return false;
}
if ($this->is_preview_mode() && !current_user_can('manage_options')) {
return false;
}
if (!empty($snippet['device_type']) && $snippet['device_type'] !== 'all') {
$device_check = $this->condition_engine->evaluate_conditions([
'rules' => [[
'type' => 'device_type',
'operator' => 'equals',
'value' => $snippet['device_type']
]]
]);
if (!$device_check) {
return false;
}
}
if (!empty($snippet['conditions'])) {
return $this->condition_engine->evaluate_conditions($snippet['conditions']);
}
return true;
}
private function render_single_snippet($snippet) {
$code = $snippet['code'];
if ($snippet['load_method'] === 'async' && $snippet['code_type'] === 'javascript') {
$code = $this->wrap_async_script($code);
} elseif ($snippet['load_method'] === 'defer' && $snippet['code_type'] === 'javascript') {
$code = $this->wrap_defer_script($code);
}
$code = apply_filters('wptag_snippet_output', $code, $snippet);
if ($this->is_preview_mode() && current_user_can('manage_options')) {
$code = $this->wrap_preview_mode($code, $snippet);
}
return $code . "\n";
}
private function wrap_async_script($code) {
if (strpos($code, '<script') === false) {
$code = '<script>' . $code . '</script>';
}
return str_replace('<script', '<script async', $code);
}
private function wrap_defer_script($code) {
if (strpos($code, '<script') === false) {
$code = '<script>' . $code . '</script>';
}
return str_replace('<script', '<script defer', $code);
}
private function wrap_preview_mode($code, $snippet) {
$name = esc_html($snippet['name']);
$id = esc_attr($snippet['id']);
return "<!-- WPTAG Preview: {$name} (ID: {$id}) -->\n{$code}\n<!-- /WPTAG Preview -->\n";
}
private function get_cache_context() {
$context = [
'type' => $this->get_page_type(),
'id' => get_the_ID(),
'user' => is_user_logged_in() ? 'logged_in' : 'logged_out'
];
if (is_user_logged_in()) {
$user = wp_get_current_user();
$context['roles'] = $user->roles;
}
return md5(json_encode($context));
}
private function get_page_type() {
if (is_home() || is_front_page()) return 'home';
if (is_single()) return 'single';
if (is_page()) return 'page';
if (is_category()) return 'category';
if (is_tag()) return 'tag';
if (is_archive()) return 'archive';
if (is_search()) return 'search';
if (is_404()) return '404';
return 'other';
}
private function is_preview_mode() {
return isset($_GET['wptag_preview']) && $_GET['wptag_preview'] === '1';
}
public function clear_output_cache() {
$positions = ['head', 'footer', 'before_content', 'after_content'];
foreach ($positions as $position) {
wp_cache_delete('wptag_output_' . $position, 'wptag');
}
}
}

View file

@ -0,0 +1,264 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPTag_Snippet_Manager {
private $table_name;
public function __construct() {
global $wpdb;
$this->table_name = $wpdb->prefix . 'wptag_snippets';
}
public function get_snippet($id) {
global $wpdb;
$snippet = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$this->table_name} WHERE id = %d",
$id
), ARRAY_A);
if ($snippet && !empty($snippet['conditions'])) {
$snippet['conditions'] = json_decode($snippet['conditions'], true);
}
return $snippet;
}
public function get_snippets($args = []) {
global $wpdb;
$defaults = [
'status' => null,
'position' => null,
'category' => null,
'search' => '',
'orderby' => 'priority',
'order' => 'ASC',
'per_page' => 20,
'page' => 1
];
$args = wp_parse_args($args, $defaults);
$where = ['1=1'];
$where_values = [];
if ($args['status'] !== null) {
$where[] = 'status = %d';
$where_values[] = $args['status'];
}
if (!empty($args['position'])) {
$where[] = 'position = %s';
$where_values[] = $args['position'];
}
if (!empty($args['category'])) {
$where[] = 'category = %s';
$where_values[] = $args['category'];
}
if (!empty($args['search'])) {
$where[] = '(name LIKE %s OR description LIKE %s)';
$search_term = '%' . $wpdb->esc_like($args['search']) . '%';
$where_values[] = $search_term;
$where_values[] = $search_term;
}
$where_clause = implode(' AND ', $where);
$orderby = sanitize_sql_orderby($args['orderby'] . ' ' . $args['order']);
$offset = ($args['page'] - 1) * $args['per_page'];
$query = "SELECT * FROM {$this->table_name} WHERE {$where_clause} ORDER BY {$orderby} LIMIT %d OFFSET %d";
$where_values[] = $args['per_page'];
$where_values[] = $offset;
$results = $wpdb->get_results($wpdb->prepare($query, $where_values), ARRAY_A);
foreach ($results as &$result) {
if (!empty($result['conditions'])) {
$result['conditions'] = json_decode($result['conditions'], true);
}
}
return $results;
}
public function get_active_snippets_by_position($position) {
global $wpdb;
$snippets = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM {$this->table_name}
WHERE status = 1 AND position = %s
ORDER BY priority ASC, id ASC",
$position
), ARRAY_A);
foreach ($snippets as &$snippet) {
if (!empty($snippet['conditions'])) {
$snippet['conditions'] = json_decode($snippet['conditions'], true);
}
}
return $snippets;
}
public function create_snippet($data) {
global $wpdb;
$snippet_data = $this->prepare_snippet_data($data);
$snippet_data['created_by'] = get_current_user_id();
$snippet_data['created_at'] = current_time('mysql');
$result = $wpdb->insert($this->table_name, $snippet_data);
if ($result === false) {
return new WP_Error('db_error', 'Failed to create snippet');
}
$snippet_id = $wpdb->insert_id;
$this->log_action('create', $snippet_id, null, $snippet_data);
return $snippet_id;
}
public function update_snippet($id, $data) {
global $wpdb;
$old_snippet = $this->get_snippet($id);
if (!$old_snippet) {
return new WP_Error('not_found', 'Snippet not found');
}
$snippet_data = $this->prepare_snippet_data($data);
$snippet_data['last_modified_by'] = get_current_user_id();
$snippet_data['updated_at'] = current_time('mysql');
$result = $wpdb->update(
$this->table_name,
$snippet_data,
['id' => $id]
);
if ($result === false) {
return new WP_Error('db_error', 'Failed to update snippet');
}
$this->log_action('update', $id, $old_snippet, $snippet_data);
$this->clear_cache();
return true;
}
public function delete_snippet($id) {
global $wpdb;
$old_snippet = $this->get_snippet($id);
if (!$old_snippet) {
return new WP_Error('not_found', 'Snippet not found');
}
$result = $wpdb->delete($this->table_name, ['id' => $id]);
if ($result === false) {
return new WP_Error('db_error', 'Failed to delete snippet');
}
$this->log_action('delete', $id, $old_snippet, null);
$this->clear_cache();
return true;
}
public function toggle_status($id) {
global $wpdb;
$snippet = $this->get_snippet($id);
if (!$snippet) {
return new WP_Error('not_found', 'Snippet not found');
}
$new_status = $snippet['status'] ? 0 : 1;
$result = $wpdb->update(
$this->table_name,
['status' => $new_status],
['id' => $id]
);
if ($result === false) {
return new WP_Error('db_error', 'Failed to update status');
}
$this->clear_cache();
return $new_status;
}
private function prepare_snippet_data($data) {
$prepared = [
'name' => sanitize_text_field($data['name'] ?? ''),
'description' => sanitize_textarea_field($data['description'] ?? ''),
'code' => $data['code'] ?? '',
'code_type' => sanitize_key($data['code_type'] ?? 'html'),
'position' => sanitize_key($data['position'] ?? 'head'),
'category' => sanitize_key($data['category'] ?? 'custom'),
'priority' => intval($data['priority'] ?? 10),
'status' => isset($data['status']) ? intval($data['status']) : 1,
'device_type' => sanitize_key($data['device_type'] ?? 'all'),
'load_method' => sanitize_key($data['load_method'] ?? 'normal')
];
if (!empty($data['conditions']) && is_array($data['conditions'])) {
$prepared['conditions'] = json_encode($data['conditions']);
} else {
$prepared['conditions'] = null;
}
return $prepared;
}
private function log_action($action, $object_id, $old_value = null, $new_value = null) {
global $wpdb;
$log_data = [
'user_id' => get_current_user_id(),
'action' => $action,
'object_type' => 'snippet',
'object_id' => $object_id,
'old_value' => $old_value ? json_encode($old_value) : null,
'new_value' => $new_value ? json_encode($new_value) : null,
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? '',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
'created_at' => current_time('mysql')
];
$wpdb->insert($wpdb->prefix . 'wptag_logs', $log_data);
}
private function clear_cache() {
wp_cache_delete('wptag_active_snippets', 'wptag');
wp_cache_delete('wptag_snippet_conditions', 'wptag');
}
public function get_categories() {
return [
'statistics' => __('Statistics', 'wptag'),
'marketing' => __('Marketing', 'wptag'),
'advertising' => __('Advertising', 'wptag'),
'seo' => __('SEO', 'wptag'),
'custom' => __('Custom', 'wptag')
];
}
public function get_positions() {
return [
'head' => __('Site Header', 'wptag'),
'footer' => __('Site Footer', 'wptag'),
'before_content' => __('Before Post Content', 'wptag'),
'after_content' => __('After Post Content', 'wptag')
];
}
}

View file

@ -0,0 +1,271 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPTag_Template_Manager {
private $table_name;
public function __construct() {
global $wpdb;
$this->table_name = $wpdb->prefix . 'wptag_templates';
}
public function get_template($service_type) {
global $wpdb;
$template = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$this->table_name} WHERE service_type = %s AND is_active = 1",
$service_type
), ARRAY_A);
if ($template && !empty($template['config_fields'])) {
$template['config_fields'] = json_decode($template['config_fields'], true);
}
return $template;
}
public function get_templates($category = null) {
global $wpdb;
$query = "SELECT * FROM {$this->table_name} WHERE is_active = 1";
$params = [];
if ($category) {
$query .= " AND service_category = %s";
$params[] = $category;
}
$query .= " ORDER BY service_category, service_name";
if (!empty($params)) {
$templates = $wpdb->get_results($wpdb->prepare($query, $params), ARRAY_A);
} else {
$templates = $wpdb->get_results($query, ARRAY_A);
}
foreach ($templates as &$template) {
if (!empty($template['config_fields'])) {
$template['config_fields'] = json_decode($template['config_fields'], true);
}
}
return $templates;
}
public function get_categories() {
return [
'analytics' => __('Analytics & Statistics', 'wptag'),
'marketing' => __('Marketing & Tracking', 'wptag'),
'seo' => __('SEO Tools', 'wptag'),
'support' => __('Customer Support', 'wptag'),
'other' => __('Other Services', 'wptag')
];
}
public function process_template_config($service_type, $config_data) {
$template = $this->get_template($service_type);
if (!$template) {
return new WP_Error('template_not_found', 'Service template not found');
}
$validated_config = $this->validate_config($template['config_fields'], $config_data);
if (is_wp_error($validated_config)) {
return $validated_config;
}
$code = $this->render_template($template['code_template'], $validated_config);
return [
'code' => $code,
'position' => $template['default_position'],
'name' => $template['service_name'] . ' - ' . ($validated_config['measurement_id'] ?? $validated_config['pixel_id'] ?? 'Config'),
'category' => $template['service_category']
];
}
private function validate_config($fields, $data) {
$validated = [];
foreach ($fields as $field) {
$field_name = $field['name'];
$field_value = $data[$field_name] ?? '';
if (!empty($field['required']) && empty($field_value)) {
return new WP_Error('missing_field', sprintf('Field %s is required', $field['label']));
}
if (!empty($field_value)) {
switch ($field['type']) {
case 'text':
$validated[$field_name] = sanitize_text_field($field_value);
break;
case 'textarea':
$validated[$field_name] = sanitize_textarea_field($field_value);
break;
case 'url':
$validated[$field_name] = esc_url_raw($field_value);
break;
case 'number':
$validated[$field_name] = intval($field_value);
break;
case 'select':
if (isset($field['options'][$field_value])) {
$validated[$field_name] = $field_value;
}
break;
default:
$validated[$field_name] = sanitize_text_field($field_value);
}
}
}
return $validated;
}
private function render_template($template, $variables) {
$code = $template;
foreach ($variables as $key => $value) {
$placeholder = '{{' . $key . '}}';
$code = str_replace($placeholder, $value, $code);
}
$code = preg_replace('/\{\{[^}]+\}\}/', '', $code);
return trim($code);
}
public function create_template($data) {
global $wpdb;
$template_data = [
'service_type' => sanitize_key($data['service_type']),
'service_name' => sanitize_text_field($data['service_name']),
'service_category' => sanitize_key($data['service_category']),
'config_fields' => json_encode($data['config_fields']),
'code_template' => $data['code_template'],
'default_position' => sanitize_key($data['default_position']),
'is_active' => 1,
'version' => sanitize_text_field($data['version'] ?? '1.0')
];
$result = $wpdb->insert($this->table_name, $template_data);
if ($result === false) {
return new WP_Error('db_error', 'Failed to create template');
}
return $wpdb->insert_id;
}
public function update_template($service_type, $data) {
global $wpdb;
$template_data = [];
if (isset($data['service_name'])) {
$template_data['service_name'] = sanitize_text_field($data['service_name']);
}
if (isset($data['service_category'])) {
$template_data['service_category'] = sanitize_key($data['service_category']);
}
if (isset($data['config_fields'])) {
$template_data['config_fields'] = json_encode($data['config_fields']);
}
if (isset($data['code_template'])) {
$template_data['code_template'] = $data['code_template'];
}
if (isset($data['default_position'])) {
$template_data['default_position'] = sanitize_key($data['default_position']);
}
if (isset($data['version'])) {
$template_data['version'] = sanitize_text_field($data['version']);
}
$template_data['updated_at'] = current_time('mysql');
$result = $wpdb->update(
$this->table_name,
$template_data,
['service_type' => $service_type]
);
if ($result === false) {
return new WP_Error('db_error', 'Failed to update template');
}
return true;
}
public function delete_template($service_type) {
global $wpdb;
$result = $wpdb->delete($this->table_name, ['service_type' => $service_type]);
if ($result === false) {
return new WP_Error('db_error', 'Failed to delete template');
}
return true;
}
public function export_templates($service_types = []) {
global $wpdb;
if (empty($service_types)) {
$templates = $wpdb->get_results("SELECT * FROM {$this->table_name}", ARRAY_A);
} else {
$placeholders = array_fill(0, count($service_types), '%s');
$query = $wpdb->prepare(
"SELECT * FROM {$this->table_name} WHERE service_type IN (" . implode(',', $placeholders) . ")",
$service_types
);
$templates = $wpdb->get_results($query, ARRAY_A);
}
return json_encode($templates, JSON_PRETTY_PRINT);
}
public function import_templates($json_data) {
$templates = json_decode($json_data, true);
if (!is_array($templates)) {
return new WP_Error('invalid_format', 'Invalid template format');
}
$imported = 0;
foreach ($templates as $template) {
if (!isset($template['service_type']) || !isset($template['code_template'])) {
continue;
}
$existing = $this->get_template($template['service_type']);
if ($existing) {
$this->update_template($template['service_type'], $template);
} else {
$this->create_template($template);
}
$imported++;
}
return $imported;
}
}

41
uninstall.php Normal file
View file

@ -0,0 +1,41 @@
<?php
if (!defined('WP_UNINSTALL_PLUGIN')) {
exit;
}
$settings = get_option('wptag_settings', []);
if (empty($settings['cleanup_on_uninstall'])) {
return;
}
global $wpdb;
$tables = [
$wpdb->prefix . 'wptag_snippets',
$wpdb->prefix . 'wptag_templates',
$wpdb->prefix . 'wptag_logs'
];
foreach ($tables as $table) {
$wpdb->query("DROP TABLE IF EXISTS {$table}");
}
$options = [
'wptag_db_version',
'wptag_settings',
'wptag_activated',
'wptag_cache_cleared'
];
foreach ($options as $option) {
delete_option($option);
}
delete_transient('wptag_admin_notice');
wp_clear_scheduled_hook('wptag_cleanup_logs');
wp_clear_scheduled_hook('wptag_cache_cleanup');
wp_cache_flush();

155
wptag-readme.md Normal file
View file

@ -0,0 +1,155 @@
# WPTAG - WordPress Code Tag Manager
Professional WordPress plugin for managing tracking codes, analytics scripts, and third-party integrations with advanced conditional loading.
## Features
### Core Functionality
- **Code Snippet Management**: Add, edit, and organize code snippets with categories
- **Multiple Insert Positions**: Head, footer, before/after content
- **Smart Conditional Loading**: Control when and where snippets appear
- **Service Templates**: Quick setup for popular services like Google Analytics, Facebook Pixel
- **Performance Optimization**: Built-in caching and code optimization
- **Import/Export**: Backup and migrate your snippets easily
### Conditional Loading Options
- Page types (home, posts, pages, archives, etc.)
- User status (logged in/out, user roles)
- Device types (desktop, mobile, tablet)
- Specific posts/pages
- Categories and tags
- URL patterns
- Date/time ranges
- Custom conditions via filters
### Supported Services Templates
- Google Analytics 4
- Facebook Pixel
- Google Ads Conversion
- Google Search Console
- Baidu Analytics
- And more...
## Installation
1. Upload the `wptag` folder to `/wp-content/plugins/`
2. Activate the plugin through the 'Plugins' menu in WordPress
3. Navigate to WPTAG in your WordPress admin
## Usage
### Creating a Snippet
1. Go to WPTAG > Code Snippets
2. Click "Add New"
3. Enter snippet details:
- Name and description
- Code content
- Position (head/footer/content)
- Category and priority
- Conditions (optional)
4. Save and activate
### Using Templates
1. Go to WPTAG > Service Templates
2. Select a service (e.g., Google Analytics)
3. Enter your configuration (e.g., Tracking ID)
4. Click "Create Snippet"
### Setting Conditions
- Add multiple conditions to control snippet visibility
- Combine conditions with AND/OR logic
- Test conditions in preview mode
## Requirements
- WordPress 6.8+
- PHP 8.0+
- MySQL 5.7+ or MariaDB 10.3+
## Database Tables
The plugin creates three tables:
- `wp_wptag_snippets` - Stores code snippets
- `wp_wptag_templates` - Service templates
- `wp_wptag_logs` - Activity logs
## Hooks and Filters
### Actions
- `wptag_before_render_snippet` - Before snippet output
- `wptag_after_render_snippet` - After snippet output
### Filters
- `wptag_snippet_output` - Modify snippet output
- `wptag_custom_condition` - Add custom conditions
- `wptag_cache_ttl` - Modify cache duration
## Performance
- Intelligent caching reduces database queries
- Conditional pre-processing for faster page loads
- Code minification option
- Compatible with popular caching plugins
## Security
- Input validation and sanitization
- XSS protection
- SQL injection prevention
- User capability checks
- Nonce verification for all actions
## Troubleshooting
### Snippets not appearing
1. Check if snippet is active
2. Verify conditions are met
3. Clear cache (WPTAG Settings > Clear Cache)
4. Enable debug mode for detailed output
### Performance issues
1. Reduce number of active snippets
2. Enable caching
3. Optimize conditions
4. Use priority settings wisely
## Developer Documentation
### Adding Custom Conditions
```php
add_filter('wptag_custom_condition', function($result, $type, $value, $operator, $context) {
if ($type === 'my_custom_condition') {
// Your condition logic here
return $result;
}
return $result;
}, 10, 5);
```
### Modifying Snippet Output
```php
add_filter('wptag_snippet_output', function($code, $snippet) {
// Modify code before output
return $code;
}, 10, 2);
```
## Support
For support and documentation, visit [wptag.com](https://wptag.com)
## License
GPL v2 or later
## Changelog
### 1.0.0
- Initial release
- Core snippet management
- Conditional loading engine
- Service templates
- Caching system
- Import/export functionality

33
wptag.php Normal file
View file

@ -0,0 +1,33 @@
<?php
/**
* Plugin Name: WPTAG
* Plugin URI: https://wptag.com
* Description: Professional WordPress code management plugin for statistics, marketing tracking, and third-party scripts
* Version: 1.0.0
* Author: WPTAG Team
* License: GPL v2 or later
* Text Domain: wptag
* Domain Path: /languages
* Requires at least: 6.8
* Requires PHP: 8.0
*/
if (!defined('ABSPATH')) {
exit;
}
define('WPTAG_VERSION', '1.0.0');
define('WPTAG_PLUGIN_FILE', __FILE__);
define('WPTAG_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('WPTAG_PLUGIN_URL', plugin_dir_url(__FILE__));
define('WPTAG_DB_VERSION', '1.0.0');
require_once WPTAG_PLUGIN_DIR . 'includes/class-wptag-core.php';
register_activation_hook(__FILE__, ['WPTag_Core', 'activate']);
register_deactivation_hook(__FILE__, ['WPTag_Core', 'deactivate']);
add_action('plugins_loaded', function() {
load_plugin_textdomain('wptag', false, dirname(plugin_basename(__FILE__)) . '/languages');
WPTag_Core::get_instance();
});

298
wptag.pot Normal file
View file

@ -0,0 +1,298 @@
# WPTAG WordPress Plugin
# Copyright (C) 2024 WPTAG Team
# This file is distributed under the GPL v2 or later.
msgid ""
msgstr ""
"Project-Id-Version: WPTAG 1.0.0\n"
"Report-Msgid-Bugs-To: https://wptag.com/support\n"
"POT-Creation-Date: 2024-01-01 00:00:00+00:00\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
#: admin/partials/dashboard.php:15
msgid "WPTAG Dashboard"
msgstr ""
#: admin/partials/dashboard.php:22
msgid "Total Snippets"
msgstr ""
#: admin/partials/dashboard.php:27
msgid "Active Snippets"
msgstr ""
#: admin/partials/dashboard.php:32
msgid "Inactive Snippets"
msgstr ""
#: admin/partials/dashboard.php:37
msgid "Success Rate"
msgstr ""
#: admin/partials/snippets.php:15
msgid "Code Snippets"
msgstr ""
#: admin/partials/snippets.php:17
msgid "Add New"
msgstr ""
#: admin/partials/snippets.php:25
msgid "Search"
msgstr ""
#: admin/partials/snippets.php:26
msgid "Search snippets..."
msgstr ""
#: admin/partials/snippets.php:31
msgid "Category"
msgstr ""
#: admin/partials/snippets.php:33
msgid "All Categories"
msgstr ""
#: admin/partials/snippets.php:43
msgid "Position"
msgstr ""
#: admin/partials/snippets.php:45
msgid "All Positions"
msgstr ""
#: admin/partials/snippets.php:55
msgid "Status"
msgstr ""
#: admin/partials/snippets.php:57
msgid "All Status"
msgstr ""
#: admin/partials/snippets.php:58
msgid "Active"
msgstr ""
#: admin/partials/snippets.php:59
msgid "Inactive"
msgstr ""
#: admin/partials/snippets.php:71
msgid "No snippets found"
msgstr ""
#: admin/partials/snippets.php:72
msgid "Get started by creating your first code snippet."
msgstr ""
#: admin/partials/snippets.php:74
msgid "Create Snippet"
msgstr ""
#: admin/partials/snippets.php:81
msgid "Name"
msgstr ""
#: admin/partials/snippets.php:82
msgid "Priority"
msgstr ""
#: admin/partials/snippets.php:83
msgid "Actions"
msgstr ""
#: admin/partials/snippets.php:115
msgid "Edit"
msgstr ""
#: admin/partials/snippets.php:119
msgid "Enable"
msgstr ""
#: admin/partials/snippets.php:119
msgid "Disable"
msgstr ""
#: admin/partials/snippets.php:122
msgid "Delete"
msgstr ""
#: admin/partials/templates.php:11
msgid "Service Templates"
msgstr ""
#: admin/partials/templates.php:17
msgid "Service templates help you quickly add popular services to your site. Simply select a template, enter your configuration details, and a snippet will be created automatically."
msgstr ""
#: admin/partials/templates.php:22
msgid "All Templates"
msgstr ""
#: admin/partials/templates.php:48
msgid "No templates found"
msgstr ""
#: admin/partials/templates.php:49
msgid "No templates available in this category."
msgstr ""
#: admin/partials/templates.php:68
msgid "Use This Template"
msgstr ""
#: admin/partials/settings.php:11
msgid "WPTAG Settings"
msgstr ""
#: admin/partials/settings.php:20
msgid "Cache Settings"
msgstr ""
#: admin/partials/settings.php:23
msgid "Enable Cache"
msgstr ""
#: admin/partials/settings.php:27
msgid "Enable caching for better performance"
msgstr ""
#: admin/partials/settings.php:30
msgid "Caching stores processed snippets and conditions to improve page load times."
msgstr ""
#: admin/partials/settings.php:37
msgid "Cache Duration"
msgstr ""
#: admin/partials/settings.php:42
msgid "seconds"
msgstr ""
#: admin/partials/settings.php:44
msgid "How long to keep cached data. Default is 3600 seconds (1 hour)."
msgstr ""
#: admin/partials/settings.php:50
msgid "Cache Status"
msgstr ""
#: admin/partials/settings.php:54
msgid "Cache Enabled:"
msgstr ""
#: admin/partials/settings.php:55
msgid "Yes"
msgstr ""
#: admin/partials/settings.php:55
msgid "No"
msgstr ""
#: admin/partials/settings.php:64
msgid "Clear Cache Now"
msgstr ""
#: admin/partials/settings.php:71
msgid "Debug Settings"
msgstr ""
#: admin/partials/settings.php:74
msgid "Debug Mode"
msgstr ""
#: admin/partials/settings.php:78
msgid "Enable debug mode"
msgstr ""
#: admin/partials/settings.php:81
msgid "Show additional information in HTML comments for troubleshooting."
msgstr ""
#: admin/partials/settings.php:87
msgid "Uninstall Settings"
msgstr ""
#: admin/partials/settings.php:90
msgid "Data Cleanup"
msgstr ""
#: admin/partials/settings.php:94
msgid "Remove all data when uninstalling the plugin"
msgstr ""
#: admin/partials/settings.php:97
msgid "Warning: This will permanently delete all snippets, templates, and settings when the plugin is uninstalled."
msgstr ""
#: admin/partials/settings.php:103
msgid "Import/Export"
msgstr ""
#: admin/partials/settings.php:106
msgid "Export Snippets"
msgstr ""
#: admin/partials/settings.php:108
msgid "Export all your snippets to a JSON file for backup or migration."
msgstr ""
#: admin/partials/settings.php:110
msgid "Export All Snippets"
msgstr ""
#: admin/partials/settings.php:116
msgid "Import Snippets"
msgstr ""
#: admin/partials/settings.php:118
msgid "Import snippets from a JSON file."
msgstr ""
#: admin/partials/settings.php:121
msgid "Import Snippets"
msgstr ""
#: admin/partials/settings.php:129
msgid "Save Settings"
msgstr ""
#: includes/class-wptag-snippet-manager.php:355
msgid "Statistics"
msgstr ""
#: includes/class-wptag-snippet-manager.php:356
msgid "Marketing"
msgstr ""
#: includes/class-wptag-snippet-manager.php:357
msgid "Advertising"
msgstr ""
#: includes/class-wptag-snippet-manager.php:358
msgid "SEO"
msgstr ""
#: includes/class-wptag-snippet-manager.php:359
msgid "Custom"
msgstr ""
#: includes/class-wptag-snippet-manager.php:365
msgid "Site Header"
msgstr ""
#: includes/class-wptag-snippet-manager.php:366
msgid "Site Footer"
msgstr ""
#: includes/class-wptag-snippet-manager.php:367
msgid "Before Post Content"
msgstr ""
#: includes/class-wptag-snippet-manager.php:368
msgid "After Post Content"
msgstr ""