upskill-event-manager/includes/class-hvac-announcements-permissions.php
Ben c20b461e7d feat: Implement secure Trainer Announcements system with comprehensive features
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>
2025-08-20 13:34:15 -03:00

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;
}
}