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>
229 lines
No EOL
9.3 KiB
PHP
229 lines
No EOL
9.3 KiB
PHP
<?php
|
|
/**
|
|
* HVAC Announcements Custom Post Type
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @since 1.0.0
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Class HVAC_Announcements_CPT
|
|
*
|
|
* Registers and manages the Trainer Announcements custom post type
|
|
*/
|
|
class HVAC_Announcements_CPT {
|
|
|
|
/**
|
|
* Post type key
|
|
*
|
|
* @var string
|
|
*/
|
|
const POST_TYPE = 'hvac_announcement';
|
|
|
|
/**
|
|
* Category taxonomy key
|
|
*
|
|
* @var string
|
|
*/
|
|
const TAXONOMY_CATEGORY = 'hvac_announcement_cat';
|
|
|
|
/**
|
|
* Tag taxonomy key
|
|
*
|
|
* @var string
|
|
*/
|
|
const TAXONOMY_TAG = 'hvac_announcement_tag';
|
|
|
|
/**
|
|
* Instance of this class
|
|
*
|
|
* @var HVAC_Announcements_CPT
|
|
*/
|
|
private static $instance = null;
|
|
|
|
/**
|
|
* Get instance of this class
|
|
*
|
|
* @return HVAC_Announcements_CPT
|
|
*/
|
|
public static function get_instance() {
|
|
if (null === self::$instance) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
private function __construct() {
|
|
$this->init_hooks();
|
|
}
|
|
|
|
/**
|
|
* Initialize hooks
|
|
*/
|
|
private function init_hooks() {
|
|
add_action('init', array($this, 'register_post_type'));
|
|
add_action('init', array($this, 'register_taxonomies'));
|
|
// Removed broken map_meta_cap filter - WordPress handles this automatically with proper capability_type
|
|
}
|
|
|
|
/**
|
|
* Register the custom post type
|
|
*/
|
|
public function register_post_type() {
|
|
$labels = array(
|
|
'name' => _x('Trainer Announcements', 'Post type general name', 'hvac'),
|
|
'singular_name' => _x('Trainer Announcement', 'Post type singular name', 'hvac'),
|
|
'menu_name' => _x('Announcements', 'Admin Menu text', 'hvac'),
|
|
'name_admin_bar' => _x('Announcement', 'Add New on Toolbar', 'hvac'),
|
|
'add_new' => __('Add New', 'hvac'),
|
|
'add_new_item' => __('Add New Announcement', 'hvac'),
|
|
'new_item' => __('New Announcement', 'hvac'),
|
|
'edit_item' => __('Edit Announcement', 'hvac'),
|
|
'view_item' => __('View Announcement', 'hvac'),
|
|
'all_items' => __('All Announcements', 'hvac'),
|
|
'search_items' => __('Search Announcements', 'hvac'),
|
|
'parent_item_colon' => __('Parent Announcements:', 'hvac'),
|
|
'not_found' => __('No announcements found.', 'hvac'),
|
|
'not_found_in_trash' => __('No announcements found in Trash.', 'hvac'),
|
|
'featured_image' => _x('Announcement Featured Image', 'Overrides the "Featured Image" phrase', 'hvac'),
|
|
'set_featured_image' => _x('Set featured image', 'Overrides the "Set featured image" phrase', 'hvac'),
|
|
'remove_featured_image' => _x('Remove featured image', 'Overrides the "Remove featured image" phrase', 'hvac'),
|
|
'use_featured_image' => _x('Use as featured image', 'Overrides the "Use as featured image" phrase', 'hvac'),
|
|
'archives' => _x('Announcement Archives', 'The post type archive label', 'hvac'),
|
|
'insert_into_item' => _x('Insert into announcement', 'Overrides the "Insert into post" phrase', 'hvac'),
|
|
'uploaded_to_this_item' => _x('Uploaded to this announcement', 'Overrides the "Uploaded to this post" phrase', 'hvac'),
|
|
'filter_items_list' => _x('Filter announcements list', 'Screen reader text', 'hvac'),
|
|
'items_list_navigation' => _x('Announcements list navigation', 'Screen reader text', 'hvac'),
|
|
'items_list' => _x('Announcements list', 'Screen reader text', 'hvac'),
|
|
);
|
|
|
|
$args = array(
|
|
'labels' => $labels,
|
|
'public' => false,
|
|
'publicly_queryable' => false,
|
|
'show_ui' => false, // We'll use custom UI
|
|
'show_in_menu' => false,
|
|
'query_var' => false,
|
|
'rewrite' => false,
|
|
'capability_type' => array('hvac_announcement', 'hvac_announcements'),
|
|
'map_meta_cap' => true,
|
|
'has_archive' => false,
|
|
'hierarchical' => false,
|
|
'menu_position' => null,
|
|
'menu_icon' => 'dashicons-megaphone',
|
|
'supports' => array('title', 'editor', 'author', 'thumbnail', 'excerpt', 'custom-fields'),
|
|
'show_in_rest' => true, // Enable Gutenberg
|
|
'rest_base' => 'hvac-announcements',
|
|
'rest_controller_class' => 'WP_REST_Posts_Controller',
|
|
);
|
|
|
|
register_post_type(self::POST_TYPE, $args);
|
|
}
|
|
|
|
/**
|
|
* Register custom taxonomies
|
|
*/
|
|
public function register_taxonomies() {
|
|
// Register Category taxonomy
|
|
$category_labels = array(
|
|
'name' => _x('Announcement Categories', 'taxonomy general name', 'hvac'),
|
|
'singular_name' => _x('Announcement Category', 'taxonomy singular name', 'hvac'),
|
|
'search_items' => __('Search Categories', 'hvac'),
|
|
'popular_items' => __('Popular Categories', 'hvac'),
|
|
'all_items' => __('All Categories', 'hvac'),
|
|
'parent_item' => null,
|
|
'parent_item_colon' => null,
|
|
'edit_item' => __('Edit Category', 'hvac'),
|
|
'update_item' => __('Update Category', 'hvac'),
|
|
'add_new_item' => __('Add New Category', 'hvac'),
|
|
'new_item_name' => __('New Category Name', 'hvac'),
|
|
'separate_items_with_commas' => __('Separate categories with commas', 'hvac'),
|
|
'add_or_remove_items' => __('Add or remove categories', 'hvac'),
|
|
'choose_from_most_used' => __('Choose from the most used categories', 'hvac'),
|
|
'not_found' => __('No categories found.', 'hvac'),
|
|
'menu_name' => __('Categories', 'hvac'),
|
|
);
|
|
|
|
$category_args = array(
|
|
'hierarchical' => true,
|
|
'labels' => $category_labels,
|
|
'show_ui' => false, // We'll manage through custom UI
|
|
'show_admin_column' => false,
|
|
'update_count_callback' => '_update_post_term_count',
|
|
'query_var' => false,
|
|
'rewrite' => false,
|
|
'show_in_rest' => true,
|
|
'rest_base' => 'hvac-announcement-categories',
|
|
);
|
|
|
|
register_taxonomy(self::TAXONOMY_CATEGORY, self::POST_TYPE, $category_args);
|
|
|
|
// Register Tag taxonomy
|
|
$tag_labels = array(
|
|
'name' => _x('Announcement Tags', 'taxonomy general name', 'hvac'),
|
|
'singular_name' => _x('Announcement Tag', 'taxonomy singular name', 'hvac'),
|
|
'search_items' => __('Search Tags', 'hvac'),
|
|
'popular_items' => __('Popular Tags', 'hvac'),
|
|
'all_items' => __('All Tags', 'hvac'),
|
|
'parent_item' => null,
|
|
'parent_item_colon' => null,
|
|
'edit_item' => __('Edit Tag', 'hvac'),
|
|
'update_item' => __('Update Tag', 'hvac'),
|
|
'add_new_item' => __('Add New Tag', 'hvac'),
|
|
'new_item_name' => __('New Tag Name', 'hvac'),
|
|
'separate_items_with_commas' => __('Separate tags with commas', 'hvac'),
|
|
'add_or_remove_items' => __('Add or remove tags', 'hvac'),
|
|
'choose_from_most_used' => __('Choose from the most used tags', 'hvac'),
|
|
'not_found' => __('No tags found.', 'hvac'),
|
|
'menu_name' => __('Tags', 'hvac'),
|
|
);
|
|
|
|
$tag_args = array(
|
|
'hierarchical' => false,
|
|
'labels' => $tag_labels,
|
|
'show_ui' => false, // We'll manage through custom UI
|
|
'show_admin_column' => false,
|
|
'update_count_callback' => '_update_post_term_count',
|
|
'query_var' => false,
|
|
'rewrite' => false,
|
|
'show_in_rest' => true,
|
|
'rest_base' => 'hvac-announcement-tags',
|
|
);
|
|
|
|
register_taxonomy(self::TAXONOMY_TAG, self::POST_TYPE, $tag_args);
|
|
}
|
|
|
|
/**
|
|
* Get post type name
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function get_post_type() {
|
|
return self::POST_TYPE;
|
|
}
|
|
|
|
/**
|
|
* Get category taxonomy name
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function get_category_taxonomy() {
|
|
return self::TAXONOMY_CATEGORY;
|
|
}
|
|
|
|
/**
|
|
* Get tag taxonomy name
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function get_tag_taxonomy() {
|
|
return self::TAXONOMY_TAG;
|
|
}
|
|
} |