diff --git a/admin/class-wptag-admin-controller.php b/admin/class-wptag-admin-controller.php new file mode 100644 index 0000000..db89a6f --- /dev/null +++ b/admin/class-wptag-admin-controller.php @@ -0,0 +1,301 @@ +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( + '

%2$s

', + $class, + esc_html($notice['message']) + ); + } +} diff --git a/admin/class-wptag-admin-interface.php b/admin/class-wptag-admin-interface.php new file mode 100644 index 0000000..e447fa2 --- /dev/null +++ b/admin/class-wptag-admin-interface.php @@ -0,0 +1,195 @@ +' . esc_html($label); + if ($required) { + echo ' *'; + } + echo ''; + } + + 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 '

' . esc_html($description) . '

'; + } + } + + private static function render_input_field($type, $name, $value, $required) { + printf( + '', + esc_attr($type), + esc_attr($name), + esc_attr($name), + esc_attr($value), + $required ? 'required' : '' + ); + } + + private static function render_textarea_field($name, $value, $required) { + printf( + '', + esc_attr($name), + esc_attr($name), + $required ? 'required' : '', + esc_textarea($value) + ); + } + + private static function render_select_field($name, $value, $options, $required) { + printf( + ''; + } + + private static function render_checkbox_field($name, $value, $label) { + printf( + '', + 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( + '
', + 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 = '') { + ?> + + 'notice-info', + 'success' => 'notice-success', + 'warning' => 'notice-warning', + 'error' => 'notice-error' + ]; + + $class = $classes[$type] ?? 'notice-info'; + + printf( + '

%s

', + esc_attr($class), + esc_html($message) + ); + } + + public static function render_tabs($tabs, $current_tab) { + echo ''; + } + + public static function render_action_buttons($actions) { + echo '
'; + + foreach ($actions as $action) { + $class = $action['primary'] ?? false ? 'button-primary' : 'button-secondary'; + $url = $action['url'] ?? '#'; + $onclick = $action['onclick'] ?? ''; + + printf( + '%s ', + esc_url($url), + esc_attr($class), + $onclick ? 'onclick="' . esc_attr($onclick) . '"' : '', + esc_html($action['label']) + ); + } + + echo '
'; + } +} diff --git a/admin/class-wptag-ajax-handler.php b/admin/class-wptag-ajax-handler.php new file mode 100644 index 0000000..8d3ddfe --- /dev/null +++ b/admin/class-wptag-ajax-handler.php @@ -0,0 +1,322 @@ +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('/]*src=["\'](?!https?:\/\/)/i', $code)) { + $errors[] = __('Use absolute URLs for external scripts', 'wptag'); + } + + return $errors; + } +} diff --git a/admin/partials/dashboard.php b/admin/partials/dashboard.php new file mode 100644 index 0000000..41b9123 --- /dev/null +++ b/admin/partials/dashboard.php @@ -0,0 +1,177 @@ +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(); +?> + +
+
+

+
+ +
+
+
+

+
+
+ +
+

+
+
+ +
+

+
+
+ +
+

+
+ 0 ? round(($active_snippets / $total_snippets) * 100) : 0; + echo $success_rate . '%'; + ?> +
+
+
+ +
+
+

+ + + + + + + + + + + + + + + + + + + + + +
+
+ +
+

+ + +

+
    +
  1. +
  2. +
  3. +
  4. +
+
+
+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+ + diff --git a/admin/partials/settings.php b/admin/partials/settings.php new file mode 100644 index 0000000..cc2f324 --- /dev/null +++ b/admin/partials/settings.php @@ -0,0 +1,245 @@ + 1, + 'cache_ttl' => 3600, + 'enable_debug' => 0, + 'cleanup_on_uninstall' => 0 +]); + +$cache_manager = new WPTag_Cache_Manager(); +$cache_stats = $cache_manager->get_cache_stats(); +?> + +
+
+

+
+ +
+
+ + + +

+ + + + + + + + + + + + + + + +
+ +

+ +

+
+ + + + +

+ +

+
+
+

+ + +

+

+ + +

+
+

+ +

+
+ +

+ + + + + +
+ +

+ +

+
+ +

+ + + + + +
+ +

+ +

+
+ +

+ + + + + + + + + + +
+

+ +
+

+ + +
+ +

+ +

+
+
+
+ + + + diff --git a/admin/partials/snippet-form.php b/admin/partials/snippet-form.php new file mode 100644 index 0000000..9990564 --- /dev/null +++ b/admin/partials/snippet-form.php @@ -0,0 +1,207 @@ +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(); +?> + +
+
+

+ + + + +

+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +

+
+ + + +

+
+ + + +
+ + + +

+
+ + + +

+
+ + + +
+ + + +

+
+ + + +
+ + + +

+
+ + + +

+
+ + +
+

+
+
+ + +
+ + + + +
+ + +
+ +
+
+
+ +

+ +

+
+
+
diff --git a/admin/partials/snippets.php b/admin/partials/snippets.php new file mode 100644 index 0000000..5c5ec9f --- /dev/null +++ b/admin/partials/snippets.php @@ -0,0 +1,149 @@ + 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(); +?> + +
+
+

+ + + + +

+
+ +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+ + + + + + + +

+

+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+ +
+ + + + + +
+ +
+
diff --git a/admin/partials/templates.php b/admin/partials/templates.php new file mode 100644 index 0000000..a6861b8 --- /dev/null +++ b/admin/partials/templates.php @@ -0,0 +1,119 @@ +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; +} +?> + +
+
+

+
+ +
+
+ +

+
+ +
+ + + + $label) : ?> + + + + +
+ + +
+ + + + + + +

+

+
+ + $cat_templates) : ?> + + +

+ +
+ +
+

+

+ +
+ v + +
+ + +
+ +
+ + +
+
+ + + + __('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'); +} +?> diff --git a/assets/admin.css b/assets/admin.css new file mode 100644 index 0000000..2b5d52e --- /dev/null +++ b/assets/admin.css @@ -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; + } +} diff --git a/assets/admin.js b/assets/admin.js new file mode 100644 index 0000000..d3ff23e --- /dev/null +++ b/assets/admin.js @@ -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 = ` +
+ + + + +
+ `; + + 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 ` +
+ + +
+ `; + }).join(''); + + const modalHtml = ` +
+
+
+

${template.service_name} Configuration

+
+
+
+ ${fields} +
+ +
+
+
+ `; + + $('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); diff --git a/includes/class-wptag-cache-manager.php b/includes/class-wptag-cache-manager.php new file mode 100644 index 0000000..c90d5ff --- /dev/null +++ b/includes/class-wptag-cache-manager.php @@ -0,0 +1,160 @@ +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(); + } +} diff --git a/includes/class-wptag-condition-engine.php b/includes/class-wptag-condition-engine.php new file mode 100644 index 0000000..1d91720 --- /dev/null +++ b/includes/class-wptag-condition-engine.php @@ -0,0 +1,311 @@ +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') + ] + ] + ]; + } +} diff --git a/includes/class-wptag-core.php b/includes/class-wptag-core.php new file mode 100644 index 0000000..eac4dbb --- /dev/null +++ b/includes/class-wptag-core.php @@ -0,0 +1,267 @@ +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' => ' +', + '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' => ' +', + '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' => ' +', + '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' => '', + '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' => '', + '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); + } + } + } +} diff --git a/includes/class-wptag-output-controller.php b/includes/class-wptag-output-controller.php new file mode 100644 index 0000000..4cdbbb9 --- /dev/null +++ b/includes/class-wptag-output-controller.php @@ -0,0 +1,178 @@ +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\n" . $output . "\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, ''; + } + + return str_replace(''; + } + + return str_replace('\n{$code}\n\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'); + } + } +} diff --git a/includes/class-wptag-snippet-manager.php b/includes/class-wptag-snippet-manager.php new file mode 100644 index 0000000..13399a2 --- /dev/null +++ b/includes/class-wptag-snippet-manager.php @@ -0,0 +1,264 @@ +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') + ]; + } +} diff --git a/includes/class-wptag-template-manager.php b/includes/class-wptag-template-manager.php new file mode 100644 index 0000000..2936647 --- /dev/null +++ b/includes/class-wptag-template-manager.php @@ -0,0 +1,271 @@ +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; + } +} diff --git a/uninstall.php b/uninstall.php new file mode 100644 index 0000000..ee4027f --- /dev/null +++ b/uninstall.php @@ -0,0 +1,41 @@ +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(); diff --git a/wptag-readme.md b/wptag-readme.md new file mode 100644 index 0000000..9c75226 --- /dev/null +++ b/wptag-readme.md @@ -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 diff --git a/wptag.php b/wptag.php new file mode 100644 index 0000000..5e26197 --- /dev/null +++ b/wptag.php @@ -0,0 +1,33 @@ +\n" +"Language-Team: LANGUAGE \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 ""