'', // Unique template identifier 'name' => '', // User-friendly template name 'description' => '', // Template description 'category' => 'general', // Template category (general, training, workshop, etc.) 'created_by' => 0, // User ID who created the template 'created_date' => '', // Creation timestamp 'modified_date' => '', // Last modification timestamp 'is_public' => false, // Whether template is available to all users 'usage_count' => 0, // Number of times template has been used 'version' => self::TEMPLATE_VERSION, 'field_data' => [], // Form field values and configuration 'validation_rules' => [], // Template-specific validation rules 'meta_data' => [] // Additional template metadata ]; /** * Loaded templates cache * * @var array */ private array $templates_cache = []; /** * Constructor */ private function __construct() { $this->init_hooks(); } /** * Initialize WordPress hooks */ private function init_hooks(): void { // AJAX endpoints for template operations add_action('wp_ajax_hvac_create_template', [$this, 'ajax_create_template']); add_action('wp_ajax_hvac_update_template', [$this, 'ajax_update_template']); add_action('wp_ajax_hvac_delete_template', [$this, 'ajax_delete_template']); add_action('wp_ajax_hvac_get_templates', [$this, 'ajax_get_templates']); add_action('wp_ajax_hvac_get_template', [$this, 'ajax_get_template']); add_action('wp_ajax_hvac_duplicate_template', [$this, 'ajax_duplicate_template']); // Admin hooks add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']); } /** * Create a new event template * * @param array $template_data Template configuration data * @return array Result with success status and template ID */ public function create_template(array $template_data): array { try { // Generate unique template ID $template_id = $this->generate_template_id(); // Prepare template data with schema defaults $template = wp_parse_args($template_data, $this->template_schema); $template['id'] = $template_id; $template['created_date'] = current_time('mysql'); $template['modified_date'] = current_time('mysql'); $template['created_by'] = get_current_user_id(); // Validate template data $validation_result = $this->validate_template($template); if (!$validation_result['valid']) { return [ 'success' => false, 'error' => 'Template validation failed: ' . implode(', ', $validation_result['errors']) ]; } // Sanitize template data $template = $this->sanitize_template($template); // Save template $save_result = $this->save_template($template); if ($save_result) { // Clear cache $this->clear_templates_cache(); return [ 'success' => true, 'template_id' => $template_id, 'message' => 'Template created successfully' ]; } else { return [ 'success' => false, 'error' => 'Failed to save template' ]; } } catch (Exception $e) { error_log('HVAC Template Manager - Create template error: ' . $e->getMessage()); return [ 'success' => false, 'error' => 'An error occurred while creating the template' ]; } } /** * Get all event templates for current user * * @param array $filters Optional filters (category, public_only, etc.) * @return array Array of templates */ public function get_templates(array $filters = []): array { $cache_key = 'all_templates_' . md5(serialize($filters) . get_current_user_id()); // Check cache first $cached_templates = wp_cache_get($cache_key, self::CACHE_GROUP); if ($cached_templates !== false) { return $cached_templates; } try { $all_templates = $this->load_templates(); $user_id = get_current_user_id(); $filtered_templates = []; foreach ($all_templates as $template) { // Apply access control if (!$template['is_public'] && $template['created_by'] !== $user_id) { // Check if user has permission to see this template if (!current_user_can('manage_options') && !$this->user_can_access_template($template)) { continue; } } // Apply filters if ($this->template_matches_filters($template, $filters)) { $filtered_templates[] = $template; } } // Sort by usage count and modification date usort($filtered_templates, function($a, $b) { if ($a['usage_count'] === $b['usage_count']) { return strtotime($b['modified_date']) - strtotime($a['modified_date']); } return $b['usage_count'] - $a['usage_count']; }); // Cache results wp_cache_set($cache_key, $filtered_templates, self::CACHE_GROUP, self::CACHE_TTL); return $filtered_templates; } catch (Exception $e) { error_log('HVAC Template Manager - Get templates error: ' . $e->getMessage()); return []; } } /** * Get a specific template by ID * * @param string $template_id Template ID * @return array|null Template data or null if not found */ public function get_template(string $template_id): ?array { $cache_key = 'template_' . $template_id; // Check cache first $cached_template = wp_cache_get($cache_key, self::CACHE_GROUP); if ($cached_template !== false) { return $cached_template; } try { $all_templates = $this->load_templates(); foreach ($all_templates as $template) { if ($template['id'] === $template_id) { // Check access permissions if (!$this->user_can_access_template($template)) { return null; } // Cache template wp_cache_set($cache_key, $template, self::CACHE_GROUP, self::CACHE_TTL); return $template; } } return null; } catch (Exception $e) { error_log('HVAC Template Manager - Get template error: ' . $e->getMessage()); return null; } } /** * Update an existing template * * @param string $template_id Template ID to update * @param array $template_data New template data * @return array Result with success status */ public function update_template(string $template_id, array $template_data): array { try { // Get existing template $existing_template = $this->get_template($template_id); if (!$existing_template) { return [ 'success' => false, 'error' => 'Template not found' ]; } // Check permissions if (!$this->user_can_edit_template($existing_template)) { return [ 'success' => false, 'error' => 'Insufficient permissions to edit this template' ]; } // Merge with existing data $updated_template = array_merge($existing_template, $template_data); $updated_template['modified_date'] = current_time('mysql'); // Validate updated template $validation_result = $this->validate_template($updated_template); if (!$validation_result['valid']) { return [ 'success' => false, 'error' => 'Template validation failed: ' . implode(', ', $validation_result['errors']) ]; } // Sanitize template data $updated_template = $this->sanitize_template($updated_template); // Save template $save_result = $this->save_template($updated_template); if ($save_result) { // Clear cache $this->clear_template_cache($template_id); $this->clear_templates_cache(); return [ 'success' => true, 'message' => 'Template updated successfully' ]; } else { return [ 'success' => false, 'error' => 'Failed to update template' ]; } } catch (Exception $e) { error_log('HVAC Template Manager - Update template error: ' . $e->getMessage()); return [ 'success' => false, 'error' => 'An error occurred while updating the template' ]; } } /** * Delete a template * * @param string $template_id Template ID to delete * @return array Result with success status */ public function delete_template(string $template_id): array { try { // Get existing template $existing_template = $this->get_template($template_id); if (!$existing_template) { return [ 'success' => false, 'error' => 'Template not found' ]; } // Check permissions if (!$this->user_can_delete_template($existing_template)) { return [ 'success' => false, 'error' => 'Insufficient permissions to delete this template' ]; } // Load all templates $all_templates = $this->load_templates(); // Remove the target template $filtered_templates = array_filter($all_templates, function($template) use ($template_id) { return $template['id'] !== $template_id; }); // Save updated template list $save_result = update_option(self::OPTION_KEY, array_values($filtered_templates)); if ($save_result) { // Clear cache $this->clear_template_cache($template_id); $this->clear_templates_cache(); return [ 'success' => true, 'message' => 'Template deleted successfully' ]; } else { return [ 'success' => false, 'error' => 'Failed to delete template' ]; } } catch (Exception $e) { error_log('HVAC Template Manager - Delete template error: ' . $e->getMessage()); return [ 'success' => false, 'error' => 'An error occurred while deleting the template' ]; } } /** * Generate a unique template ID * * @return string Unique template ID */ private function generate_template_id(): string { return 'hvac_template_' . uniqid() . '_' . time(); } /** * Validate template data * * @param array $template Template data to validate * @return array Validation result with 'valid' boolean and 'errors' array */ private function validate_template(array $template): array { $errors = []; // Required fields if (empty($template['name'])) { $errors[] = 'Template name is required'; } if (strlen($template['name']) > 100) { $errors[] = 'Template name must be 100 characters or less'; } if (strlen($template['description']) > 500) { $errors[] = 'Template description must be 500 characters or less'; } // Validate category $valid_categories = ['general', 'training', 'workshop', 'certification', 'webinar']; if (!in_array($template['category'], $valid_categories)) { $errors[] = 'Invalid template category'; } // Validate field data structure if (!is_array($template['field_data'])) { $errors[] = 'Field data must be an array'; } // Validate user permissions for public templates if ($template['is_public'] && !current_user_can('manage_options')) { $errors[] = 'Only administrators can create public templates'; } return [ 'valid' => empty($errors), 'errors' => $errors ]; } /** * Sanitize template data * * @param array $template Template data to sanitize * @return array Sanitized template data */ private function sanitize_template(array $template): array { $sanitized = []; $sanitized['id'] = sanitize_text_field($template['id']); $sanitized['name'] = sanitize_text_field($template['name']); $sanitized['description'] = sanitize_textarea_field($template['description']); $sanitized['category'] = sanitize_text_field($template['category']); $sanitized['created_by'] = absint($template['created_by']); $sanitized['created_date'] = sanitize_text_field($template['created_date']); $sanitized['modified_date'] = sanitize_text_field($template['modified_date']); $sanitized['is_public'] = (bool) $template['is_public']; $sanitized['usage_count'] = absint($template['usage_count']); $sanitized['version'] = sanitize_text_field($template['version']); $sanitized['field_data'] = $this->sanitize_field_data($template['field_data']); $sanitized['validation_rules'] = $this->sanitize_validation_rules($template['validation_rules']); $sanitized['meta_data'] = $this->sanitize_meta_data($template['meta_data']); return $sanitized; } /** * Sanitize field data array * * @param array $field_data Field data to sanitize * @return array Sanitized field data */ private function sanitize_field_data(array $field_data): array { $sanitized = []; foreach ($field_data as $key => $value) { $sanitized_key = sanitize_text_field($key); if (is_array($value)) { $sanitized[$sanitized_key] = array_map('sanitize_text_field', $value); } else { $sanitized[$sanitized_key] = sanitize_text_field($value); } } return $sanitized; } /** * Sanitize validation rules * * @param array $validation_rules Validation rules to sanitize * @return array Sanitized validation rules */ private function sanitize_validation_rules(array $validation_rules): array { // Implementation for validation rule sanitization return array_map('sanitize_text_field', $validation_rules); } /** * Sanitize meta data * * @param array $meta_data Meta data to sanitize * @return array Sanitized meta data */ private function sanitize_meta_data(array $meta_data): array { $sanitized = []; foreach ($meta_data as $key => $value) { $sanitized_key = sanitize_text_field($key); if (is_array($value)) { $sanitized[$sanitized_key] = array_map('sanitize_text_field', $value); } else { $sanitized[$sanitized_key] = sanitize_text_field($value); } } return $sanitized; } /** * Load all templates from WordPress options * * @return array Array of templates */ private function load_templates(): array { if (!empty($this->templates_cache)) { return $this->templates_cache; } $templates = get_option(self::OPTION_KEY, []); // Ensure templates are arrays and have required structure $templates = array_filter($templates, function($template) { return is_array($template) && !empty($template['id']); }); $this->templates_cache = $templates; return $templates; } /** * Save a template to storage * * @param array $template Template data to save * @return bool Success status */ private function save_template(array $template): bool { $all_templates = $this->load_templates(); // Find and replace existing template or add new one $template_found = false; foreach ($all_templates as $index => $existing_template) { if ($existing_template['id'] === $template['id']) { $all_templates[$index] = $template; $template_found = true; break; } } if (!$template_found) { $all_templates[] = $template; } return update_option(self::OPTION_KEY, $all_templates); } /** * Check if user can access a template * * @param array $template Template data * @return bool Whether user can access the template */ private function user_can_access_template(array $template): bool { $user_id = get_current_user_id(); // Template owner can always access if ($template['created_by'] === $user_id) { return true; } // Public templates are accessible to all authenticated users if ($template['is_public']) { return is_user_logged_in(); } // Administrators can access all templates if (current_user_can('manage_options')) { return true; } return false; } /** * Check if user can edit a template * * @param array $template Template data * @return bool Whether user can edit the template */ private function user_can_edit_template(array $template): bool { $user_id = get_current_user_id(); // Template owner can edit if ($template['created_by'] === $user_id) { return true; } // Administrators can edit all templates if (current_user_can('manage_options')) { return true; } return false; } /** * Check if user can delete a template * * @param array $template Template data * @return bool Whether user can delete the template */ private function user_can_delete_template(array $template): bool { $user_id = get_current_user_id(); // Template owner can delete if ($template['created_by'] === $user_id) { return true; } // Administrators can delete all templates if (current_user_can('manage_options')) { return true; } return false; } /** * Check if template matches filters * * @param array $template Template data * @param array $filters Filter criteria * @return bool Whether template matches filters */ private function template_matches_filters(array $template, array $filters): bool { foreach ($filters as $key => $value) { switch ($key) { case 'category': if ($template['category'] !== $value) { return false; } break; case 'public_only': if ($value && !$template['is_public']) { return false; } break; case 'search': if (stripos($template['name'], $value) === false && stripos($template['description'], $value) === false) { return false; } break; } } return true; } /** * Clear template cache for specific template * * @param string $template_id Template ID */ private function clear_template_cache(string $template_id): void { wp_cache_delete('template_' . $template_id, self::CACHE_GROUP); } /** * Clear all templates cache */ private function clear_templates_cache(): void { // Clear object cache $this->templates_cache = []; // Clear WordPress cache for all possible cache keys // Since we can't enumerate all cache keys, we increment cache version $cache_version = get_option('hvac_template_cache_version', 1); update_option('hvac_template_cache_version', $cache_version + 1); } /** * AJAX handler for creating templates */ public function ajax_create_template(): void { // Security check if (!wp_verify_nonce($_POST['nonce'] ?? '', 'hvac_template_nonce')) { wp_send_json_error(['message' => 'Security check failed']); return; } // Permission check if (!is_user_logged_in()) { wp_send_json_error(['message' => 'Authentication required']); return; } $template_data = $_POST['template_data'] ?? []; $result = $this->create_template($template_data); if ($result['success']) { wp_send_json_success($result); } else { wp_send_json_error($result); } } /** * AJAX handler for getting templates */ public function ajax_get_templates(): void { // Security check if (!wp_verify_nonce($_GET['nonce'] ?? '', 'hvac_template_nonce')) { wp_send_json_error(['message' => 'Security check failed']); return; } // Permission check if (!is_user_logged_in()) { wp_send_json_error(['message' => 'Authentication required']); return; } $filters = $_GET['filters'] ?? []; $templates = $this->get_templates($filters); wp_send_json_success([ 'templates' => $templates, 'count' => count($templates) ]); } /** * AJAX handler for getting single template */ public function ajax_get_template(): void { // Security check if (!wp_verify_nonce($_GET['nonce'] ?? '', 'hvac_template_nonce')) { wp_send_json_error(['message' => 'Security check failed']); return; } // Permission check if (!is_user_logged_in()) { wp_send_json_error(['message' => 'Authentication required']); return; } $template_id = $_GET['template_id'] ?? ''; if (empty($template_id)) { wp_send_json_error(['message' => 'Template ID required']); return; } $template = $this->get_template($template_id); if ($template) { wp_send_json_success(['template' => $template]); } else { wp_send_json_error(['message' => 'Template not found or access denied']); } } /** * AJAX handler for updating templates */ public function ajax_update_template(): void { // Security check if (!wp_verify_nonce($_POST['nonce'] ?? '', 'hvac_template_nonce')) { wp_send_json_error(['message' => 'Security check failed']); return; } // Permission check if (!is_user_logged_in()) { wp_send_json_error(['message' => 'Authentication required']); return; } $template_id = $_POST['template_id'] ?? ''; $template_data = $_POST['template_data'] ?? []; if (empty($template_id)) { wp_send_json_error(['message' => 'Template ID required']); return; } $result = $this->update_template($template_id, $template_data); if ($result['success']) { wp_send_json_success($result); } else { wp_send_json_error($result); } } /** * AJAX handler for deleting templates */ public function ajax_delete_template(): void { // Security check if (!wp_verify_nonce($_POST['nonce'] ?? '', 'hvac_template_nonce')) { wp_send_json_error(['message' => 'Security check failed']); return; } // Permission check if (!is_user_logged_in()) { wp_send_json_error(['message' => 'Authentication required']); return; } $template_id = $_POST['template_id'] ?? ''; if (empty($template_id)) { wp_send_json_error(['message' => 'Template ID required']); return; } $result = $this->delete_template($template_id); if ($result['success']) { wp_send_json_success($result); } else { wp_send_json_error($result); } } /** * AJAX handler for duplicating templates */ public function ajax_duplicate_template(): void { // Security check if (!wp_verify_nonce($_POST['nonce'] ?? '', 'hvac_template_nonce')) { wp_send_json_error(['message' => 'Security check failed']); return; } // Permission check if (!is_user_logged_in()) { wp_send_json_error(['message' => 'Authentication required']); return; } $template_id = $_POST['template_id'] ?? ''; if (empty($template_id)) { wp_send_json_error(['message' => 'Template ID required']); return; } // Get original template $original_template = $this->get_template($template_id); if (!$original_template) { wp_send_json_error(['message' => 'Template not found']); return; } // Create duplicate with modified name $duplicate_data = $original_template; $duplicate_data['name'] = $original_template['name'] . ' (Copy)'; unset($duplicate_data['id']); // Remove ID so new one is generated $result = $this->create_template($duplicate_data); if ($result['success']) { wp_send_json_success($result); } else { wp_send_json_error($result); } } /** * Enqueue admin scripts for template management */ public function enqueue_admin_scripts(): void { // Only load on relevant pages if (!$this->should_load_admin_scripts()) { return; } wp_enqueue_script( 'hvac-template-manager', HVAC_PLUGIN_URL . 'assets/js/hvac-template-manager.js', ['jquery'], HVAC_VERSION, true ); // Localize script with AJAX configuration wp_localize_script('hvac-template-manager', 'hvacTemplates', [ 'ajaxurl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('hvac_template_nonce'), 'strings' => [ 'confirmDelete' => 'Are you sure you want to delete this template?', 'templateSaved' => 'Template saved successfully', 'templateDeleted' => 'Template deleted successfully', 'error' => 'An error occurred. Please try again.', ] ]); } /** * Check if admin scripts should be loaded * * @return bool */ private function should_load_admin_scripts(): bool { global $pagenow; // Load on event management pages if (in_array($pagenow, ['admin.php', 'edit.php', 'post.php', 'post-new.php'])) { return true; } // Load on template management pages $current_page = $_GET['page'] ?? ''; if (strpos($current_page, 'hvac') !== false) { return true; } return false; } }