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>
362 lines
No EOL
12 KiB
PHP
362 lines
No EOL
12 KiB
PHP
<?php
|
|
/**
|
|
* HVAC Announcements Permissions
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @since 1.0.0
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Class HVAC_Announcements_Permissions
|
|
*
|
|
* Manages role-based permissions for the announcements system
|
|
*/
|
|
class HVAC_Announcements_Permissions {
|
|
|
|
/**
|
|
* Instance of this class
|
|
*
|
|
* @var HVAC_Announcements_Permissions
|
|
*/
|
|
private static $instance = null;
|
|
|
|
/**
|
|
* Get instance of this class
|
|
*
|
|
* @return HVAC_Announcements_Permissions
|
|
*/
|
|
public static function get_instance() {
|
|
if (null === self::$instance) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
private function __construct() {
|
|
// Permissions are set up during plugin activation
|
|
$this->init_hooks();
|
|
}
|
|
|
|
/**
|
|
* Initialize hooks for cache invalidation
|
|
*/
|
|
private function init_hooks() {
|
|
// Clear cache when user roles change
|
|
add_action('set_user_role', array($this, 'clear_trainers_cache'));
|
|
add_action('add_user_role', array($this, 'clear_trainers_cache'));
|
|
add_action('remove_user_role', array($this, 'clear_trainers_cache'));
|
|
|
|
// Clear cache when user meta changes (for account_status)
|
|
add_action('updated_user_meta', array($this, 'maybe_clear_trainers_cache'), 10, 4);
|
|
add_action('added_user_meta', array($this, 'maybe_clear_trainers_cache'), 10, 4);
|
|
add_action('deleted_user_meta', array($this, 'maybe_clear_trainers_cache'), 10, 4);
|
|
|
|
// Clear cache when user is deleted
|
|
add_action('deleted_user', array($this, 'clear_trainers_cache'));
|
|
}
|
|
|
|
/**
|
|
* Clear the active trainers cache
|
|
*/
|
|
public function clear_trainers_cache() {
|
|
wp_cache_delete('hvac_active_trainers', 'hvac_announcements');
|
|
}
|
|
|
|
/**
|
|
* Maybe clear trainers cache when user meta changes
|
|
*
|
|
* @param int $meta_id Meta ID
|
|
* @param int $user_id User ID
|
|
* @param string $meta_key Meta key
|
|
* @param mixed $meta_value Meta value
|
|
*/
|
|
public function maybe_clear_trainers_cache($meta_id, $user_id, $meta_key, $meta_value) {
|
|
// Clear cache if account_status or email opt-out changes
|
|
if (in_array($meta_key, array('account_status', 'hvac_announcement_emails_opt_out'))) {
|
|
$this->clear_trainers_cache();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add announcement capabilities to roles
|
|
* Called during plugin activation
|
|
*/
|
|
public static function add_capabilities() {
|
|
// Get roles
|
|
$master_trainer = get_role('hvac_master_trainer');
|
|
$trainer = get_role('hvac_trainer');
|
|
$administrator = get_role('administrator');
|
|
|
|
// Master Trainer capabilities (full access)
|
|
if ($master_trainer) {
|
|
// Reading
|
|
$master_trainer->add_cap('read_hvac_announcements');
|
|
$master_trainer->add_cap('read_private_hvac_announcements');
|
|
|
|
// Creating
|
|
$master_trainer->add_cap('edit_hvac_announcements');
|
|
$master_trainer->add_cap('edit_others_hvac_announcements');
|
|
$master_trainer->add_cap('edit_private_hvac_announcements');
|
|
$master_trainer->add_cap('edit_published_hvac_announcements');
|
|
$master_trainer->add_cap('publish_hvac_announcements');
|
|
|
|
// Deleting
|
|
$master_trainer->add_cap('delete_hvac_announcements');
|
|
$master_trainer->add_cap('delete_others_hvac_announcements');
|
|
$master_trainer->add_cap('delete_private_hvac_announcements');
|
|
$master_trainer->add_cap('delete_published_hvac_announcements');
|
|
|
|
// Terms
|
|
$master_trainer->add_cap('manage_hvac_announcement_terms');
|
|
$master_trainer->add_cap('edit_hvac_announcement_terms');
|
|
$master_trainer->add_cap('delete_hvac_announcement_terms');
|
|
$master_trainer->add_cap('assign_hvac_announcement_terms');
|
|
}
|
|
|
|
// Regular Trainer capabilities (read only)
|
|
if ($trainer) {
|
|
$trainer->add_cap('read_hvac_announcements');
|
|
// Note: NOT adding read_private capability for regular trainers
|
|
}
|
|
|
|
// Administrator gets all capabilities
|
|
if ($administrator) {
|
|
// Reading
|
|
$administrator->add_cap('read_hvac_announcements');
|
|
$administrator->add_cap('read_private_hvac_announcements');
|
|
|
|
// Creating
|
|
$administrator->add_cap('edit_hvac_announcements');
|
|
$administrator->add_cap('edit_others_hvac_announcements');
|
|
$administrator->add_cap('edit_private_hvac_announcements');
|
|
$administrator->add_cap('edit_published_hvac_announcements');
|
|
$administrator->add_cap('publish_hvac_announcements');
|
|
|
|
// Deleting
|
|
$administrator->add_cap('delete_hvac_announcements');
|
|
$administrator->add_cap('delete_others_hvac_announcements');
|
|
$administrator->add_cap('delete_private_hvac_announcements');
|
|
$administrator->add_cap('delete_published_hvac_announcements');
|
|
|
|
// Terms
|
|
$administrator->add_cap('manage_hvac_announcement_terms');
|
|
$administrator->add_cap('edit_hvac_announcement_terms');
|
|
$administrator->add_cap('delete_hvac_announcement_terms');
|
|
$administrator->add_cap('assign_hvac_announcement_terms');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove announcement capabilities from roles
|
|
* Called during plugin deactivation
|
|
*/
|
|
public static function remove_capabilities() {
|
|
// Get roles
|
|
$master_trainer = get_role('hvac_master_trainer');
|
|
$trainer = get_role('hvac_trainer');
|
|
$administrator = get_role('administrator');
|
|
|
|
// List of all capabilities
|
|
$capabilities = array(
|
|
'read_hvac_announcements',
|
|
'read_private_hvac_announcements',
|
|
'edit_hvac_announcements',
|
|
'edit_others_hvac_announcements',
|
|
'edit_private_hvac_announcements',
|
|
'edit_published_hvac_announcements',
|
|
'publish_hvac_announcements',
|
|
'delete_hvac_announcements',
|
|
'delete_others_hvac_announcements',
|
|
'delete_private_hvac_announcements',
|
|
'delete_published_hvac_announcements',
|
|
'manage_hvac_announcement_terms',
|
|
'edit_hvac_announcement_terms',
|
|
'delete_hvac_announcement_terms',
|
|
'assign_hvac_announcement_terms',
|
|
);
|
|
|
|
// Remove from each role
|
|
foreach ($capabilities as $cap) {
|
|
if ($master_trainer) {
|
|
$master_trainer->remove_cap($cap);
|
|
}
|
|
if ($trainer) {
|
|
$trainer->remove_cap($cap);
|
|
}
|
|
if ($administrator) {
|
|
$administrator->remove_cap($cap);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if current user can create announcements
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function current_user_can_create() {
|
|
return current_user_can('publish_hvac_announcements');
|
|
}
|
|
|
|
/**
|
|
* Check if current user can edit announcements
|
|
*
|
|
* @param int $post_id Optional post ID to check specific announcement
|
|
* @return bool
|
|
*/
|
|
public static function current_user_can_edit($post_id = 0) {
|
|
if ($post_id) {
|
|
// Use WordPress core capability for specific post
|
|
return current_user_can('edit_post', $post_id);
|
|
}
|
|
return current_user_can('edit_hvac_announcements');
|
|
}
|
|
|
|
/**
|
|
* Check if current user can delete announcements
|
|
*
|
|
* @param int $post_id Optional post ID to check specific announcement
|
|
* @return bool
|
|
*/
|
|
public static function current_user_can_delete($post_id = 0) {
|
|
if ($post_id) {
|
|
// Use WordPress core capability for specific post
|
|
return current_user_can('delete_post', $post_id);
|
|
}
|
|
return current_user_can('delete_hvac_announcements');
|
|
}
|
|
|
|
/**
|
|
* Check if current user can read announcements
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function current_user_can_read() {
|
|
return current_user_can('read_hvac_announcements');
|
|
}
|
|
|
|
/**
|
|
* Check if user is a master trainer
|
|
*
|
|
* @param int $user_id Optional user ID, defaults to current user
|
|
* @return bool
|
|
*/
|
|
public static function is_master_trainer($user_id = 0) {
|
|
if (!$user_id) {
|
|
$user_id = get_current_user_id();
|
|
}
|
|
|
|
$user = get_user_by('id', $user_id);
|
|
if (!$user) {
|
|
return false;
|
|
}
|
|
|
|
return in_array('hvac_master_trainer', $user->roles) || in_array('administrator', $user->roles);
|
|
}
|
|
|
|
/**
|
|
* Check if user is a trainer (regular or master)
|
|
*
|
|
* @param int $user_id Optional user ID, defaults to current user
|
|
* @return bool
|
|
*/
|
|
public static function is_trainer($user_id = 0) {
|
|
if (!$user_id) {
|
|
$user_id = get_current_user_id();
|
|
}
|
|
|
|
$user = get_user_by('id', $user_id);
|
|
if (!$user) {
|
|
return false;
|
|
}
|
|
|
|
$trainer_roles = array('hvac_trainer', 'hvac_master_trainer', 'administrator');
|
|
return !empty(array_intersect($trainer_roles, $user->roles));
|
|
}
|
|
|
|
/**
|
|
* Get active trainers for email notifications
|
|
* Excludes disabled, deactivated, or pending users
|
|
*
|
|
* @return array Array of user objects
|
|
*/
|
|
public static function get_active_trainers() {
|
|
// Check cache first
|
|
$cache_key = 'hvac_active_trainers';
|
|
$cached = wp_cache_get($cache_key, 'hvac_announcements');
|
|
|
|
if ($cached !== false) {
|
|
return $cached;
|
|
}
|
|
|
|
$args = array(
|
|
'role__in' => array('hvac_trainer', 'hvac_master_trainer'),
|
|
'meta_query' => array(
|
|
'relation' => 'AND',
|
|
array(
|
|
'relation' => 'OR',
|
|
array(
|
|
'key' => 'account_status',
|
|
'value' => array('disabled', 'deactivated', 'pending'),
|
|
'compare' => 'NOT IN'
|
|
),
|
|
array(
|
|
'key' => 'account_status',
|
|
'compare' => 'NOT EXISTS'
|
|
)
|
|
)
|
|
)
|
|
);
|
|
|
|
$users = get_users($args);
|
|
|
|
// Cache for 5 minutes
|
|
wp_cache_set($cache_key, $users, 'hvac_announcements', 300);
|
|
|
|
return $users;
|
|
}
|
|
|
|
/**
|
|
* Check if user should receive announcement emails
|
|
*
|
|
* @param int $user_id User ID
|
|
* @return bool
|
|
*/
|
|
public static function user_should_receive_emails($user_id) {
|
|
$user = get_user_by('id', $user_id);
|
|
if (!$user) {
|
|
return false;
|
|
}
|
|
|
|
// Validate user has a valid email address
|
|
if (!$user->user_email || !is_email($user->user_email)) {
|
|
return false;
|
|
}
|
|
|
|
// Check if user is a trainer
|
|
if (!self::is_trainer($user_id)) {
|
|
return false;
|
|
}
|
|
|
|
// Check account status
|
|
$account_status = get_user_meta($user_id, 'account_status', true);
|
|
if (in_array($account_status, array('disabled', 'deactivated', 'pending'))) {
|
|
return false;
|
|
}
|
|
|
|
// Check if user has opted out of emails (future feature)
|
|
$email_opt_out = get_user_meta($user_id, 'hvac_announcement_emails_opt_out', true);
|
|
if ($email_opt_out === 'yes') {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
} |