This commit introduces a complete announcement management system for HVAC trainers with enterprise-grade security, performance optimization, and email notifications. ## Core Features - Custom post type for trainer announcements with categories and tags - Role-based permissions (master trainers can create/edit, all trainers can read) - AJAX-powered admin interface with real-time updates - Modal popup viewing for announcements on frontend - Automated email notifications when announcements are published - Google Drive integration for training resources ## Security Enhancements - Fixed critical capability mapping bug preventing proper permission checks - Added content disclosure protection for draft/private announcements - Fixed XSS vulnerabilities with proper output escaping and sanitization - Implemented permission checks on all AJAX endpoints - Added rate limiting to prevent abuse (30 requests/minute) - Email validation before sending notifications ## Performance Optimizations - Implemented intelligent caching for user queries (5-minute TTL) - Added cache versioning for announcement lists (2-minute TTL) - Automatic cache invalidation on content changes - Batch email processing to prevent timeouts (50 emails per batch) - Retry mechanism for failed email sends (max 3 attempts) ## Technical Implementation - Singleton pattern for all manager classes - WordPress coding standards compliance - Proper nonce verification on all AJAX requests - Comprehensive error handling and logging - Mobile-responsive UI with smooth animations - WCAG accessibility compliance ## Components Added - 6 PHP classes for modular architecture - 2 page templates (master announcements, trainer resources) - Admin and frontend JavaScript with jQuery integration - Comprehensive CSS for both admin and frontend - Email notification system with HTML templates - Complete documentation and implementation plans This system provides a secure, scalable foundation for trainer communications while following WordPress best practices and maintaining high code quality. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
194 lines
No EOL
8.9 KiB
PHP
194 lines
No EOL
8.9 KiB
PHP
<?php
|
|
/**
|
|
* Template Name: Master Trainer Announcements
|
|
* Description: Manage trainer announcements (Master Trainers only)
|
|
*
|
|
* @package HVAC_Community_Events
|
|
*/
|
|
|
|
// Security check
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
// Define template constant
|
|
if (!defined('HVAC_IN_PAGE_TEMPLATE')) {
|
|
define('HVAC_IN_PAGE_TEMPLATE', true);
|
|
}
|
|
|
|
// Check if user is a master trainer
|
|
if (!HVAC_Announcements_Permissions::is_master_trainer()) {
|
|
wp_redirect(home_url('/trainer/dashboard/'));
|
|
exit;
|
|
}
|
|
|
|
get_header();
|
|
|
|
// Get menu system instance
|
|
$menu_system = HVAC_Menu_System::get_instance();
|
|
?>
|
|
|
|
<div class="hvac-plugin-page hvac-master-announcements-page">
|
|
<?php
|
|
// Display navigation menu
|
|
echo $menu_system->render_navigation_menu();
|
|
?>
|
|
|
|
<!-- Breadcrumbs -->
|
|
<nav class="hvac-breadcrumbs" aria-label="Breadcrumb">
|
|
<div class="container">
|
|
<ol class="breadcrumb-list">
|
|
<li class="breadcrumb-item"><a href="<?php echo home_url(); ?>">Home</a></li>
|
|
<li class="breadcrumb-item"><a href="<?php echo home_url('/master-trainer/master-dashboard/'); ?>">Master Dashboard</a></li>
|
|
<li class="breadcrumb-item active" aria-current="page">Announcements</li>
|
|
</ol>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="container">
|
|
<div class="hvac-announcements-wrapper">
|
|
<header class="page-header">
|
|
<h1><?php _e('Trainer Announcements', 'hvac'); ?></h1>
|
|
<button id="add-announcement-btn" class="button button-primary">
|
|
<span class="dashicons dashicons-plus-alt"></span>
|
|
<?php _e('Add New Announcement', 'hvac'); ?>
|
|
</button>
|
|
</header>
|
|
|
|
<!-- Filters and Search -->
|
|
<div class="announcements-controls">
|
|
<div class="filter-group">
|
|
<label for="status-filter"><?php _e('Status:', 'hvac'); ?></label>
|
|
<select id="status-filter" class="filter-select">
|
|
<option value="any"><?php _e('All', 'hvac'); ?></option>
|
|
<option value="publish"><?php _e('Published', 'hvac'); ?></option>
|
|
<option value="draft"><?php _e('Draft', 'hvac'); ?></option>
|
|
<option value="private"><?php _e('Private', 'hvac'); ?></option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="search-group">
|
|
<input type="text" id="announcement-search" placeholder="<?php esc_attr_e('Search announcements...', 'hvac'); ?>" />
|
|
<button id="search-btn" class="button">
|
|
<span class="dashicons dashicons-search"></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Announcements Table -->
|
|
<div class="announcements-table-wrapper">
|
|
<table id="announcements-table" class="wp-list-table widefat fixed striped">
|
|
<thead>
|
|
<tr>
|
|
<th class="column-title"><?php _e('Title', 'hvac'); ?></th>
|
|
<th class="column-status"><?php _e('Status', 'hvac'); ?></th>
|
|
<th class="column-categories"><?php _e('Categories', 'hvac'); ?></th>
|
|
<th class="column-author"><?php _e('Author', 'hvac'); ?></th>
|
|
<th class="column-date"><?php _e('Date', 'hvac'); ?></th>
|
|
<th class="column-actions"><?php _e('Actions', 'hvac'); ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="announcements-list">
|
|
<!-- Populated via AJAX -->
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Pagination -->
|
|
<div class="announcements-pagination">
|
|
<button id="prev-page" class="button" disabled>
|
|
<span class="dashicons dashicons-arrow-left-alt2"></span>
|
|
<?php _e('Previous', 'hvac'); ?>
|
|
</button>
|
|
<span class="page-info">
|
|
<?php _e('Page', 'hvac'); ?> <span id="current-page">1</span> <?php _e('of', 'hvac'); ?> <span id="total-pages">1</span>
|
|
</span>
|
|
<button id="next-page" class="button">
|
|
<?php _e('Next', 'hvac'); ?>
|
|
<span class="dashicons dashicons-arrow-right-alt2"></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add/Edit Announcement Modal -->
|
|
<div id="announcement-modal" class="hvac-modal" style="display: none;">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h2 id="modal-title"><?php _e('Add New Announcement', 'hvac'); ?></h2>
|
|
<button class="modal-close">×</button>
|
|
</div>
|
|
|
|
<form id="announcement-form">
|
|
<div class="modal-body">
|
|
<input type="hidden" id="announcement-id" value="" />
|
|
|
|
<div class="form-group">
|
|
<label for="announcement-title"><?php _e('Title', 'hvac'); ?> <span class="required">*</span></label>
|
|
<input type="text" id="announcement-title" name="title" required />
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="announcement-content"><?php _e('Content', 'hvac'); ?></label>
|
|
<div id="announcement-content-editor"></div>
|
|
<textarea id="announcement-content" name="content" style="display: none;"></textarea>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="announcement-excerpt"><?php _e('Excerpt', 'hvac'); ?></label>
|
|
<textarea id="announcement-excerpt" name="excerpt" rows="3"></textarea>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label for="announcement-status"><?php _e('Status', 'hvac'); ?></label>
|
|
<select id="announcement-status" name="status">
|
|
<option value="draft"><?php _e('Draft', 'hvac'); ?></option>
|
|
<option value="publish"><?php _e('Published', 'hvac'); ?></option>
|
|
<option value="private"><?php _e('Private', 'hvac'); ?></option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="announcement-date"><?php _e('Publish Date', 'hvac'); ?></label>
|
|
<input type="datetime-local" id="announcement-date" name="publish_date" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="announcement-categories"><?php _e('Categories', 'hvac'); ?></label>
|
|
<div id="categories-container">
|
|
<!-- Populated via AJAX -->
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="announcement-tags"><?php _e('Tags', 'hvac'); ?></label>
|
|
<input type="text" id="announcement-tags" name="tags" placeholder="<?php esc_attr_e('Separate tags with commas', 'hvac'); ?>" />
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label><?php _e('Featured Image', 'hvac'); ?></label>
|
|
<div class="featured-image-container">
|
|
<div id="featured-image-preview"></div>
|
|
<button type="button" id="select-featured-image" class="button">
|
|
<?php _e('Select Image', 'hvac'); ?>
|
|
</button>
|
|
<button type="button" id="remove-featured-image" class="button" style="display: none;">
|
|
<?php _e('Remove Image', 'hvac'); ?>
|
|
</button>
|
|
<input type="hidden" id="featured-image-id" name="featured_image_id" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal-footer">
|
|
<button type="button" class="button modal-cancel"><?php _e('Cancel', 'hvac'); ?></button>
|
|
<button type="submit" class="button button-primary"><?php _e('Save Announcement', 'hvac'); ?></button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<?php get_footer(); ?>
|