init_hooks(); } /** * Initialize AJAX hooks */ private function init_hooks() { // AJAX actions for logged-in users add_action('wp_ajax_hvac_get_announcements', array($this, 'get_announcements')); add_action('wp_ajax_hvac_get_announcement', array($this, 'get_announcement')); add_action('wp_ajax_hvac_create_announcement', array($this, 'create_announcement')); add_action('wp_ajax_hvac_update_announcement', array($this, 'update_announcement')); add_action('wp_ajax_hvac_delete_announcement', array($this, 'delete_announcement')); add_action('wp_ajax_hvac_get_announcement_categories', array($this, 'get_categories')); add_action('wp_ajax_hvac_get_announcement_tags', array($this, 'get_tags')); add_action('wp_ajax_hvac_view_announcement', array($this, 'view_announcement')); // Clear cache when announcements are modified add_action('save_post_' . HVAC_Announcements_CPT::get_post_type(), array($this, 'clear_announcements_cache')); add_action('delete_post', array($this, 'maybe_clear_announcements_cache')); add_action('transition_post_status', array($this, 'clear_announcements_cache_on_status_change'), 10, 3); } /** * Clear all announcements caches */ public function clear_announcements_cache() { // Clear all cached announcement lists by flushing the group // Since we can't iterate cache keys in standard WP, we'll use a version key approach $version = wp_cache_get('announcements_cache_version', 'hvac_announcements'); if (false === $version) { $version = 1; } else { $version++; } wp_cache_set('announcements_cache_version', $version, 'hvac_announcements', 0); } /** * Maybe clear announcements cache when a post is deleted * * @param int $post_id Post ID */ public function maybe_clear_announcements_cache($post_id) { $post = get_post($post_id); if ($post && $post->post_type === HVAC_Announcements_CPT::get_post_type()) { $this->clear_announcements_cache(); } } /** * Clear cache when announcement status changes * * @param string $new_status New status * @param string $old_status Old status * @param WP_Post $post Post object */ public function clear_announcements_cache_on_status_change($new_status, $old_status, $post) { if ($post->post_type === HVAC_Announcements_CPT::get_post_type()) { $this->clear_announcements_cache(); } } /** * Check rate limiting for AJAX requests * * @return bool True if within limits, sends error and exits if exceeded */ private function check_rate_limit() { $user_id = get_current_user_id(); $transient_key = 'hvac_ajax_rate_' . $user_id; $request_count = get_transient($transient_key); if (false === $request_count) { $request_count = 0; } // Allow 30 requests per minute if ($request_count >= 30) { wp_send_json_error('Rate limit exceeded. Please wait a moment and try again.'); exit; } // Increment and set transient for 60 seconds set_transient($transient_key, $request_count + 1, 60); return true; } /** * Get paginated announcements */ public function get_announcements() { // Check rate limiting $this->check_rate_limit(); // Verify nonce if (!check_ajax_referer('hvac_announcements_nonce', 'nonce', false)) { wp_send_json_error('Invalid security token'); } // Check permissions if (!HVAC_Announcements_Permissions::current_user_can_read()) { wp_send_json_error('Insufficient permissions'); } // Get parameters $page = isset($_POST['page']) ? intval($_POST['page']) : 1; $per_page = isset($_POST['per_page']) ? intval($_POST['per_page']) : 20; $status = isset($_POST['status']) ? sanitize_text_field($_POST['status']) : 'any'; $search = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : ''; // Build cache key based on parameters, user role, and cache version $is_master = HVAC_Announcements_Permissions::is_master_trainer(); $cache_version = wp_cache_get('announcements_cache_version', 'hvac_announcements'); if (false === $cache_version) { $cache_version = 1; } $cache_key = 'hvac_announcements_v' . $cache_version . '_' . md5(serialize(array( 'page' => $page, 'per_page' => $per_page, 'status' => $status, 'search' => $search, 'is_master' => $is_master ))); // Try to get from cache $cached_response = wp_cache_get($cache_key, 'hvac_announcements'); if ($cached_response !== false && empty($search)) { // Don't cache search results wp_send_json_success($cached_response); return; } // Build query args $args = array( 'post_type' => HVAC_Announcements_CPT::get_post_type(), 'posts_per_page' => $per_page, 'paged' => $page, 'orderby' => 'date', 'order' => 'DESC', ); // Filter by status if ($status !== 'any') { $args['post_status'] = $status; } else { $args['post_status'] = array('publish', 'draft', 'private'); } // Add search if (!empty($search)) { $args['s'] = $search; } // For non-master trainers, only show published announcements if (!$is_master) { $args['post_status'] = 'publish'; } // Query announcements $query = new WP_Query($args); $announcements = array(); if ($query->have_posts()) { while ($query->have_posts()) { $query->the_post(); $categories = wp_get_post_terms(get_the_ID(), HVAC_Announcements_CPT::get_category_taxonomy(), array('fields' => 'names')); $tags = wp_get_post_terms(get_the_ID(), HVAC_Announcements_CPT::get_tag_taxonomy(), array('fields' => 'names')); $announcements[] = array( 'id' => get_the_ID(), 'title' => get_the_title(), 'excerpt' => wp_kses_post(get_the_excerpt()), // Sanitize HTML content 'status' => get_post_status(), 'date' => get_the_date('Y-m-d H:i:s'), 'author' => get_the_author(), 'categories' => $categories, 'tags' => $tags, 'featured_image' => get_the_post_thumbnail_url(get_the_ID(), 'thumbnail'), 'can_edit' => HVAC_Announcements_Permissions::current_user_can_edit(get_the_ID()), 'can_delete' => HVAC_Announcements_Permissions::current_user_can_delete(get_the_ID()), ); } wp_reset_postdata(); } $response = array( 'announcements' => $announcements, 'total' => $query->found_posts, 'pages' => $query->max_num_pages, 'current_page' => $page, ); // Cache the response if not a search query (cache for 2 minutes) if (empty($search)) { wp_cache_set($cache_key, $response, 'hvac_announcements', 120); } wp_send_json_success($response); } /** * Get single announcement for editing */ public function get_announcement() { // Check rate limiting $this->check_rate_limit(); // Verify nonce if (!check_ajax_referer('hvac_announcements_nonce', 'nonce', false)) { wp_send_json_error('Invalid security token'); } $post_id = isset($_POST['id']) ? intval($_POST['id']) : 0; if (!$post_id) { wp_send_json_error('Invalid announcement ID'); } // Check permissions if (!HVAC_Announcements_Permissions::current_user_can_edit($post_id)) { wp_send_json_error('Insufficient permissions'); } $post = get_post($post_id); if (!$post || $post->post_type !== HVAC_Announcements_CPT::get_post_type()) { wp_send_json_error('Announcement not found'); } $categories = wp_get_post_terms($post_id, HVAC_Announcements_CPT::get_category_taxonomy(), array('fields' => 'ids')); $tags = wp_get_post_terms($post_id, HVAC_Announcements_CPT::get_tag_taxonomy(), array('fields' => 'names')); $announcement = array( 'id' => $post->ID, 'title' => $post->post_title, 'content' => $post->post_content, 'excerpt' => $post->post_excerpt, 'status' => $post->post_status, 'date' => $post->post_date, 'categories' => $categories, 'tags' => implode(', ', $tags), 'featured_image_id' => get_post_thumbnail_id($post->ID), 'featured_image_url' => get_the_post_thumbnail_url($post->ID, 'medium'), ); wp_send_json_success($announcement); } /** * Create new announcement */ public function create_announcement() { // Check rate limiting $this->check_rate_limit(); // Verify nonce if (!check_ajax_referer('hvac_announcements_nonce', 'nonce', false)) { wp_send_json_error('Invalid security token'); } // Check permissions if (!HVAC_Announcements_Permissions::current_user_can_create()) { wp_send_json_error('Insufficient permissions'); } // Validate required fields if (empty($_POST['title'])) { wp_send_json_error('Title is required'); } // Prepare post data $post_data = array( 'post_title' => sanitize_text_field($_POST['title']), 'post_content' => wp_kses_post($_POST['content']), 'post_excerpt' => sanitize_textarea_field($_POST['excerpt']), 'post_status' => sanitize_text_field($_POST['status']), 'post_type' => HVAC_Announcements_CPT::get_post_type(), 'post_author' => get_current_user_id(), ); // Set publish date if provided if (!empty($_POST['publish_date'])) { $post_data['post_date'] = sanitize_text_field($_POST['publish_date']); $post_data['post_date_gmt'] = get_gmt_from_date($post_data['post_date']); } // Create post $post_id = wp_insert_post($post_data); if (is_wp_error($post_id)) { wp_send_json_error($post_id->get_error_message()); } // Set categories if (!empty($_POST['categories'])) { $categories = array_map('intval', (array) $_POST['categories']); wp_set_post_terms($post_id, $categories, HVAC_Announcements_CPT::get_category_taxonomy()); } // Set tags if (!empty($_POST['tags'])) { $tags = array_map('trim', explode(',', sanitize_text_field($_POST['tags']))); wp_set_post_terms($post_id, $tags, HVAC_Announcements_CPT::get_tag_taxonomy()); } // Set featured image if (!empty($_POST['featured_image_id'])) { set_post_thumbnail($post_id, intval($_POST['featured_image_id'])); } wp_send_json_success(array( 'id' => $post_id, 'message' => __('Announcement created successfully', 'hvac'), )); } /** * Update existing announcement */ public function update_announcement() { // Check rate limiting $this->check_rate_limit(); // Verify nonce if (!check_ajax_referer('hvac_announcements_nonce', 'nonce', false)) { wp_send_json_error('Invalid security token'); } $post_id = isset($_POST['id']) ? intval($_POST['id']) : 0; if (!$post_id) { wp_send_json_error('Invalid announcement ID'); } // Check permissions if (!HVAC_Announcements_Permissions::current_user_can_edit($post_id)) { wp_send_json_error('Insufficient permissions'); } // Validate required fields if (empty($_POST['title'])) { wp_send_json_error('Title is required'); } // Prepare post data $post_data = array( 'ID' => $post_id, 'post_title' => sanitize_text_field($_POST['title']), 'post_content' => wp_kses_post($_POST['content']), 'post_excerpt' => sanitize_textarea_field($_POST['excerpt']), 'post_status' => sanitize_text_field($_POST['status']), ); // Set publish date if provided if (!empty($_POST['publish_date'])) { $post_data['post_date'] = sanitize_text_field($_POST['publish_date']); $post_data['post_date_gmt'] = get_gmt_from_date($post_data['post_date']); } // Update post $result = wp_update_post($post_data); if (is_wp_error($result)) { wp_send_json_error($result->get_error_message()); } // Update categories if (isset($_POST['categories'])) { $categories = array_map('intval', (array) $_POST['categories']); wp_set_post_terms($post_id, $categories, HVAC_Announcements_CPT::get_category_taxonomy()); } // Update tags if (isset($_POST['tags'])) { $tags = array_map('trim', explode(',', sanitize_text_field($_POST['tags']))); wp_set_post_terms($post_id, $tags, HVAC_Announcements_CPT::get_tag_taxonomy()); } // Update featured image if (isset($_POST['featured_image_id'])) { if (empty($_POST['featured_image_id'])) { delete_post_thumbnail($post_id); } else { set_post_thumbnail($post_id, intval($_POST['featured_image_id'])); } } wp_send_json_success(array( 'id' => $post_id, 'message' => __('Announcement updated successfully', 'hvac'), )); } /** * Delete announcement */ public function delete_announcement() { // Check rate limiting $this->check_rate_limit(); // Verify nonce if (!check_ajax_referer('hvac_announcements_nonce', 'nonce', false)) { wp_send_json_error('Invalid security token'); } $post_id = isset($_POST['id']) ? intval($_POST['id']) : 0; if (!$post_id) { wp_send_json_error('Invalid announcement ID'); } // Check permissions if (!HVAC_Announcements_Permissions::current_user_can_delete($post_id)) { wp_send_json_error('Insufficient permissions'); } // Delete post $result = wp_delete_post($post_id, true); // Force delete if (!$result) { wp_send_json_error('Failed to delete announcement'); } wp_send_json_success(array( 'message' => __('Announcement deleted successfully', 'hvac'), )); } /** * Get categories for select options */ public function get_categories() { // Check rate limiting $this->check_rate_limit(); // Verify nonce if (!check_ajax_referer('hvac_announcements_nonce', 'nonce', false)) { wp_send_json_error('Invalid security token'); } // Check permissions - only users who can create announcements need category list if (!HVAC_Announcements_Permissions::current_user_can_create()) { wp_send_json_error('Insufficient permissions'); } $categories = get_terms(array( 'taxonomy' => HVAC_Announcements_CPT::get_category_taxonomy(), 'hide_empty' => false, )); $options = array(); if (!is_wp_error($categories)) { foreach ($categories as $category) { $options[] = array( 'id' => $category->term_id, 'name' => $category->name, ); } } wp_send_json_success($options); } /** * Get tags for autocomplete */ public function get_tags() { // Check rate limiting $this->check_rate_limit(); // Verify nonce if (!check_ajax_referer('hvac_announcements_nonce', 'nonce', false)) { wp_send_json_error('Invalid security token'); } // Check permissions - only users who can create announcements need tag list if (!HVAC_Announcements_Permissions::current_user_can_create()) { wp_send_json_error('Insufficient permissions'); } $tags = get_terms(array( 'taxonomy' => HVAC_Announcements_CPT::get_tag_taxonomy(), 'hide_empty' => false, )); $tag_names = array(); if (!is_wp_error($tags)) { foreach ($tags as $tag) { $tag_names[] = $tag->name; } } wp_send_json_success($tag_names); } /** * Get announcement content for modal viewing */ public function view_announcement() { // Check rate limiting $this->check_rate_limit(); // Verify nonce if (!check_ajax_referer('hvac_announcements_nonce', 'nonce', false)) { wp_send_json_error('Invalid security token'); } $post_id = isset($_POST['id']) ? intval($_POST['id']) : 0; if (!$post_id) { wp_send_json_error('Invalid announcement ID'); } // Check permissions - only need read permission for viewing if (!HVAC_Announcements_Permissions::current_user_can_read()) { wp_send_json_error('Insufficient permissions'); } // Check post status - only allow published posts for non-master trainers $post = get_post($post_id); if (!$post || $post->post_type !== HVAC_Announcements_CPT::get_post_type()) { wp_send_json_error('Announcement not found'); } // Only allow viewing of published posts unless user is a master trainer if ($post->post_status !== 'publish' && !HVAC_Announcements_Permissions::is_master_trainer()) { wp_send_json_error('You do not have permission to view this announcement'); } // Get announcement data $title = get_the_title($post); $content = apply_filters('the_content', $post->post_content); $date = get_the_date('F j, Y', $post); $author = get_the_author_meta('display_name', $post->post_author); wp_send_json_success(array( 'title' => $title, 'content' => $content, 'date' => $date, 'author' => $author )); } }