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>
431 lines
No EOL
16 KiB
PHP
431 lines
No EOL
16 KiB
PHP
<?php
|
|
/**
|
|
* HVAC Announcements Display Handler
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @since 1.0.0
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Class HVAC_Announcements_Display
|
|
*
|
|
* Handles frontend display and formatting of announcements
|
|
*/
|
|
class HVAC_Announcements_Display {
|
|
|
|
/**
|
|
* Instance of this class
|
|
*
|
|
* @var HVAC_Announcements_Display
|
|
*/
|
|
private static $instance = null;
|
|
|
|
/**
|
|
* Get instance of this class
|
|
*
|
|
* @return HVAC_Announcements_Display
|
|
*/
|
|
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() {
|
|
// Register shortcodes
|
|
add_shortcode('hvac_announcements_timeline', array($this, 'render_timeline_shortcode'));
|
|
add_shortcode('hvac_announcements_list', array($this, 'render_list_shortcode'));
|
|
add_shortcode('hvac_google_drive_embed', array($this, 'render_google_drive_shortcode'));
|
|
|
|
// Filter for UAGB post timeline to include our post type
|
|
add_filter('uagb_post_timeline_query_args', array($this, 'modify_uagb_query'), 10, 2);
|
|
|
|
// Enqueue scripts when shortcode is used
|
|
add_action('wp_enqueue_scripts', array($this, 'maybe_enqueue_scripts'));
|
|
}
|
|
|
|
/**
|
|
* Render announcements timeline shortcode
|
|
*
|
|
* @param array $atts Shortcode attributes
|
|
* @return string
|
|
*/
|
|
public function render_timeline_shortcode($atts) {
|
|
// Check permissions
|
|
if (!HVAC_Announcements_Permissions::current_user_can_read()) {
|
|
return '<p>' . __('You do not have permission to view announcements.', 'hvac') . '</p>';
|
|
}
|
|
|
|
$atts = shortcode_atts(array(
|
|
'posts_per_page' => 10,
|
|
'orderby' => 'date',
|
|
'order' => 'DESC',
|
|
), $atts);
|
|
|
|
// Query announcements
|
|
$args = array(
|
|
'post_type' => HVAC_Announcements_CPT::get_post_type(),
|
|
'posts_per_page' => intval($atts['posts_per_page']),
|
|
'orderby' => sanitize_text_field($atts['orderby']),
|
|
'order' => sanitize_text_field($atts['order']),
|
|
'post_status' => 'publish',
|
|
);
|
|
|
|
$query = new WP_Query($args);
|
|
|
|
ob_start();
|
|
?>
|
|
<div class="hvac-announcements-timeline">
|
|
<?php if ($query->have_posts()) : ?>
|
|
<div class="timeline-wrapper">
|
|
<?php while ($query->have_posts()) : $query->the_post(); ?>
|
|
<article class="timeline-item">
|
|
<div class="timeline-marker"></div>
|
|
<div class="timeline-content">
|
|
<header class="timeline-header">
|
|
<h3 class="timeline-title">
|
|
<a href="#" class="announcement-link" data-id="<?php echo esc_attr(get_the_ID()); ?>">
|
|
<?php the_title(); ?>
|
|
</a>
|
|
</h3>
|
|
<div class="timeline-meta">
|
|
<span class="timeline-date"><?php echo esc_html(get_the_date()); ?></span>
|
|
<span class="timeline-author"><?php echo esc_html(get_the_author()); ?></span>
|
|
</div>
|
|
</header>
|
|
|
|
<?php if (has_post_thumbnail()) : ?>
|
|
<div class="timeline-thumbnail">
|
|
<?php the_post_thumbnail('medium'); ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<div class="timeline-excerpt">
|
|
<?php the_excerpt(); ?>
|
|
</div>
|
|
|
|
<?php
|
|
$categories = wp_get_post_terms(get_the_ID(), HVAC_Announcements_CPT::get_category_taxonomy());
|
|
if (!empty($categories)) :
|
|
?>
|
|
<div class="timeline-categories">
|
|
<?php foreach ($categories as $category) : ?>
|
|
<span class="category-badge"><?php echo esc_html($category->name); ?></span>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</article>
|
|
<?php endwhile; ?>
|
|
</div>
|
|
|
|
<?php if ($query->max_num_pages > 1) : ?>
|
|
<div class="timeline-pagination">
|
|
<button class="load-more-announcements" data-page="2" data-max="<?php echo esc_attr($query->max_num_pages); ?>">
|
|
<?php _e('Load More Announcements', 'hvac'); ?>
|
|
</button>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php else : ?>
|
|
<div class="no-announcements">
|
|
<p><?php _e('No announcements have been posted yet.', 'hvac'); ?></p>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php wp_reset_postdata(); ?>
|
|
</div>
|
|
|
|
<!-- Modal for viewing announcement -->
|
|
<div id="announcement-modal" class="hvac-modal" style="display: none;">
|
|
<div class="modal-content">
|
|
<span class="modal-close">×</span>
|
|
<div class="modal-body">
|
|
<!-- Content loaded via AJAX -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
|
|
return ob_get_clean();
|
|
}
|
|
|
|
/**
|
|
* Render announcements list shortcode
|
|
*
|
|
* @param array $atts Shortcode attributes
|
|
* @return string
|
|
*/
|
|
public function render_list_shortcode($atts) {
|
|
// Check permissions
|
|
if (!HVAC_Announcements_Permissions::current_user_can_read()) {
|
|
return '<p>' . __('You do not have permission to view announcements.', 'hvac') . '</p>';
|
|
}
|
|
|
|
$atts = shortcode_atts(array(
|
|
'posts_per_page' => 5,
|
|
'show_excerpt' => 'yes',
|
|
'show_date' => 'yes',
|
|
'show_author' => 'no',
|
|
), $atts);
|
|
|
|
// Query announcements
|
|
$args = array(
|
|
'post_type' => HVAC_Announcements_CPT::get_post_type(),
|
|
'posts_per_page' => intval($atts['posts_per_page']),
|
|
'orderby' => 'date',
|
|
'order' => 'DESC',
|
|
'post_status' => 'publish',
|
|
);
|
|
|
|
$query = new WP_Query($args);
|
|
|
|
ob_start();
|
|
?>
|
|
<div class="hvac-announcements-list">
|
|
<?php if ($query->have_posts()) : ?>
|
|
<ul class="announcements-list">
|
|
<?php while ($query->have_posts()) : $query->the_post(); ?>
|
|
<li class="announcement-item">
|
|
<h4 class="announcement-title">
|
|
<a href="#" class="announcement-link" data-id="<?php echo esc_attr(get_the_ID()); ?>">
|
|
<?php the_title(); ?>
|
|
</a>
|
|
</h4>
|
|
|
|
<?php if ($atts['show_date'] === 'yes' || $atts['show_author'] === 'yes') : ?>
|
|
<div class="announcement-meta">
|
|
<?php if ($atts['show_date'] === 'yes') : ?>
|
|
<span class="announcement-date"><?php echo esc_html(get_the_date()); ?></span>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($atts['show_author'] === 'yes') : ?>
|
|
<span class="announcement-author"><?php echo esc_html(get_the_author()); ?></span>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($atts['show_excerpt'] === 'yes') : ?>
|
|
<div class="announcement-excerpt">
|
|
<?php the_excerpt(); ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</li>
|
|
<?php endwhile; ?>
|
|
</ul>
|
|
<?php else : ?>
|
|
<p><?php _e('No announcements have been posted yet.', 'hvac'); ?></p>
|
|
<?php endif; ?>
|
|
|
|
<?php wp_reset_postdata(); ?>
|
|
</div>
|
|
<?php
|
|
|
|
return ob_get_clean();
|
|
}
|
|
|
|
/**
|
|
* Render Google Drive embed shortcode
|
|
*
|
|
* @param array $atts Shortcode attributes
|
|
* @return string
|
|
*/
|
|
public function render_google_drive_shortcode($atts) {
|
|
// Check permissions
|
|
if (!HVAC_Announcements_Permissions::is_trainer()) {
|
|
return '<p>' . __('You do not have permission to view training resources.', 'hvac') . '</p>';
|
|
}
|
|
|
|
$atts = shortcode_atts(array(
|
|
'url' => 'https://drive.google.com/drive/folders/16uDRkFcaEqKUxfBek9VbfbAIeFV77nZG?usp=drive_link',
|
|
'height' => '600',
|
|
'width' => '100%',
|
|
), $atts);
|
|
|
|
// Convert sharing URL to embed URL
|
|
$embed_url = $this->convert_drive_url_to_embed($atts['url']);
|
|
|
|
ob_start();
|
|
?>
|
|
<div class="hvac-google-drive-embed">
|
|
<iframe
|
|
src="<?php echo esc_url($embed_url); ?>"
|
|
width="<?php echo esc_attr($atts['width']); ?>"
|
|
height="<?php echo esc_attr($atts['height']); ?>"
|
|
frameborder="0"
|
|
allowfullscreen="true"
|
|
mozallowfullscreen="true"
|
|
webkitallowfullscreen="true">
|
|
</iframe>
|
|
</div>
|
|
<?php
|
|
|
|
return ob_get_clean();
|
|
}
|
|
|
|
/**
|
|
* Convert Google Drive sharing URL to embed URL
|
|
*
|
|
* @param string $url Sharing URL
|
|
* @return string Embed URL
|
|
*/
|
|
private function convert_drive_url_to_embed($url) {
|
|
// Extract folder ID from URL
|
|
if (preg_match('/\/folders\/([a-zA-Z0-9-_]+)/', $url, $matches)) {
|
|
$folder_id = $matches[1];
|
|
return 'https://drive.google.com/embeddedfolderview?id=' . $folder_id . '#list';
|
|
}
|
|
|
|
// Return original URL if pattern doesn't match
|
|
return $url;
|
|
}
|
|
|
|
/**
|
|
* Modify UAGB query to include our post type
|
|
*
|
|
* @param array $query_args Query arguments
|
|
* @param array $attributes Block attributes
|
|
* @return array
|
|
*/
|
|
public function modify_uagb_query($query_args, $attributes) {
|
|
// Check if this is for announcements
|
|
if (isset($attributes['post_type']) && $attributes['post_type'] === 'hvac_announcement') {
|
|
$query_args['post_type'] = HVAC_Announcements_CPT::get_post_type();
|
|
|
|
// Only show published posts to non-master trainers
|
|
if (!HVAC_Announcements_Permissions::is_master_trainer()) {
|
|
$query_args['post_status'] = 'publish';
|
|
}
|
|
}
|
|
|
|
return $query_args;
|
|
}
|
|
|
|
/**
|
|
* Get single announcement content for modal
|
|
*
|
|
* @param int $post_id Announcement ID
|
|
* @return string
|
|
*/
|
|
public static function get_announcement_content($post_id) {
|
|
$post = get_post($post_id);
|
|
|
|
if (!$post || $post->post_type !== HVAC_Announcements_CPT::get_post_type()) {
|
|
return '';
|
|
}
|
|
|
|
// Check permissions
|
|
if (!HVAC_Announcements_Permissions::current_user_can_read()) {
|
|
return '';
|
|
}
|
|
|
|
// Only allow viewing of published posts unless user is a master trainer
|
|
if ($post->post_status !== 'publish' && !HVAC_Announcements_Permissions::is_master_trainer()) {
|
|
return '';
|
|
}
|
|
|
|
ob_start();
|
|
?>
|
|
<article class="announcement-full">
|
|
<header class="announcement-header">
|
|
<h2><?php echo esc_html($post->post_title); ?></h2>
|
|
<div class="announcement-meta">
|
|
<span class="date"><?php echo esc_html(get_the_date('F j, Y', $post)); ?></span>
|
|
<span class="author"><?php echo esc_html(get_the_author_meta('display_name', $post->post_author)); ?></span>
|
|
</div>
|
|
</header>
|
|
|
|
<?php if (has_post_thumbnail($post->ID)) : ?>
|
|
<div class="announcement-featured-image">
|
|
<?php echo get_the_post_thumbnail($post->ID, 'large'); ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<div class="announcement-content">
|
|
<?php echo apply_filters('the_content', $post->post_content); ?>
|
|
</div>
|
|
|
|
<?php
|
|
$categories = wp_get_post_terms($post->ID, HVAC_Announcements_CPT::get_category_taxonomy());
|
|
$tags = wp_get_post_terms($post->ID, HVAC_Announcements_CPT::get_tag_taxonomy());
|
|
|
|
if (!empty($categories) || !empty($tags)) :
|
|
?>
|
|
<footer class="announcement-footer">
|
|
<?php if (!empty($categories)) : ?>
|
|
<div class="announcement-categories">
|
|
<strong><?php _e('Categories:', 'hvac'); ?></strong>
|
|
<?php
|
|
$category_names = wp_list_pluck($categories, 'name');
|
|
echo esc_html(implode(', ', $category_names));
|
|
?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if (!empty($tags)) : ?>
|
|
<div class="announcement-tags">
|
|
<strong><?php _e('Tags:', 'hvac'); ?></strong>
|
|
<?php
|
|
$tag_names = wp_list_pluck($tags, 'name');
|
|
echo esc_html(implode(', ', $tag_names));
|
|
?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</footer>
|
|
<?php endif; ?>
|
|
</article>
|
|
<?php
|
|
|
|
return ob_get_clean();
|
|
}
|
|
|
|
/**
|
|
* Maybe enqueue scripts if announcements are displayed
|
|
*/
|
|
public function maybe_enqueue_scripts() {
|
|
global $post;
|
|
|
|
// Check if any of our shortcodes are present
|
|
if (is_a($post, 'WP_Post') && (
|
|
has_shortcode($post->post_content, 'hvac_announcements_timeline') ||
|
|
has_shortcode($post->post_content, 'hvac_announcements_list')
|
|
)) {
|
|
// Enqueue the view script
|
|
wp_enqueue_script(
|
|
'hvac-announcements-view',
|
|
plugin_dir_url(dirname(__FILE__)) . 'assets/js/hvac-announcements-view.js',
|
|
array('jquery'),
|
|
defined('HVAC_VERSION') ? HVAC_VERSION : '1.0.0',
|
|
true
|
|
);
|
|
|
|
// Localize script
|
|
wp_localize_script('hvac-announcements-view', 'hvac_ajax', array(
|
|
'ajax_url' => admin_url('admin-ajax.php'),
|
|
'nonce' => wp_create_nonce('hvac_announcements_nonce'),
|
|
));
|
|
|
|
// Also enqueue the CSS
|
|
wp_enqueue_style(
|
|
'hvac-announcements',
|
|
plugin_dir_url(dirname(__FILE__)) . 'assets/css/hvac-announcements.css',
|
|
array(),
|
|
defined('HVAC_VERSION') ? HVAC_VERSION : '1.0.0'
|
|
);
|
|
}
|
|
}
|
|
}
|