• Add user role field to registration, profile display, and profile edit - 10 role options: technician, installer, supervisor, manager, trainer, consultant, sales rep, engineer, business owner, other - Required field with server-side validation - Radio buttons in registration, dropdown in profile edit - Displays in profile with proper capitalization • Implement advanced certification tracking system - Date Certified: HTML5 date picker with validation (no future dates) - Certification Type: dropdown with "Certified measureQuick Trainer" and "Certified measureQuick Champion" - Certification Status: color-coded status badges (Active/Expired/Pending/Disabled) • Add sophisticated role-based access control - Regular trainers: read-only access to certification fields - Administrators & master trainers: full edit access to certification fields - Visual indicators for read-only fields - Server-side permission validation • Enhance plugin activation system - Initialize all 36 user meta fields for existing users - Smart default assignment based on user capabilities - Backward compatibility maintained • Add professional UI styling - Blue-bordered certification section with trophy icon - Color-coded status badges with proper contrast - Read-only field styling with visual indicators - Enhanced form controls with focus states • Comprehensive testing and documentation - E2E test coverage with visual verification - Updated API reference with new meta fields - Access control patterns documented - 100% test pass rate on staging environment 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
550 lines
No EOL
22 KiB
PHP
550 lines
No EOL
22 KiB
PHP
<?php
|
|
/**
|
|
* HVAC Venues Management
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @since 2.0.0
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* HVAC_Venues class
|
|
*/
|
|
class HVAC_Venues {
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public function __construct() {
|
|
// Register shortcodes
|
|
add_shortcode('hvac_trainer_venues_list', array($this, 'render_venues_list'));
|
|
add_shortcode('hvac_trainer_venue_manage', array($this, 'render_venue_manage'));
|
|
|
|
// Enqueue scripts
|
|
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
|
|
|
|
// Handle AJAX requests
|
|
add_action('wp_ajax_hvac_save_venue', array($this, 'ajax_save_venue'));
|
|
add_action('wp_ajax_hvac_delete_venue', array($this, 'ajax_delete_venue'));
|
|
add_action('wp_ajax_hvac_load_venue', array($this, 'ajax_load_venue'));
|
|
}
|
|
|
|
/**
|
|
* Enqueue scripts and styles
|
|
*/
|
|
public function enqueue_scripts() {
|
|
if (is_page('trainer/venue/list') || is_page('trainer/venue/manage')) {
|
|
wp_enqueue_style(
|
|
'hvac-venues-style',
|
|
HVAC_PLUGIN_URL . 'assets/css/hvac-venues.css',
|
|
array(),
|
|
HVAC_PLUGIN_VERSION
|
|
);
|
|
|
|
wp_enqueue_script(
|
|
'hvac-venues-js',
|
|
HVAC_PLUGIN_URL . 'assets/js/hvac-venues.js',
|
|
array('jquery'),
|
|
HVAC_PLUGIN_VERSION,
|
|
true
|
|
);
|
|
|
|
wp_localize_script('hvac-venues-js', 'hvacVenues', array(
|
|
'ajax_url' => admin_url('admin-ajax.php'),
|
|
'nonce' => wp_create_nonce('hvac_venues_nonce')
|
|
));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render venues list
|
|
*/
|
|
public function render_venues_list() {
|
|
if (!is_user_logged_in()) {
|
|
return '<p>You must be logged in to view this page.</p>';
|
|
}
|
|
|
|
// Allow trainers, master trainers, or WordPress admins
|
|
if (!current_user_can('hvac_trainer') && !current_user_can('hvac_master_trainer') && !current_user_can('manage_options')) {
|
|
return '<p>You must be a trainer to view this page.</p>';
|
|
}
|
|
|
|
ob_start();
|
|
?>
|
|
<div class="hvac-venues-list">
|
|
<div class="hvac-page-header">
|
|
<h1>Training Venues</h1>
|
|
<a href="/trainer/venue/manage/" class="hvac-button hvac-button-primary">Add New Venue</a>
|
|
</div>
|
|
|
|
<?php
|
|
// Breadcrumbs are handled by page template when using WordPress pages
|
|
if (!defined('HVAC_IN_PAGE_TEMPLATE') && class_exists('HVAC_Breadcrumbs')) {
|
|
echo HVAC_Breadcrumbs::instance()->render_breadcrumbs();
|
|
}
|
|
?>
|
|
|
|
<?php $this->render_venues_table(); ?>
|
|
</div>
|
|
<?php
|
|
return ob_get_clean();
|
|
}
|
|
|
|
/**
|
|
* Render venues table
|
|
*/
|
|
private function render_venues_table() {
|
|
global $wpdb;
|
|
|
|
$current_user_id = get_current_user_id();
|
|
|
|
// Get pagination parameters
|
|
$page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
|
|
$per_page = 20;
|
|
$offset = ($page - 1) * $per_page;
|
|
|
|
// Build query
|
|
$query_args = array(
|
|
'post_type' => class_exists('Tribe__Events__Main') ? Tribe__Events__Main::VENUE_POST_TYPE : 'tribe_venue',
|
|
'posts_per_page' => $per_page,
|
|
'offset' => $offset,
|
|
'orderby' => 'title',
|
|
'order' => 'ASC',
|
|
'post_status' => 'publish'
|
|
);
|
|
|
|
// Filter handling
|
|
if (!empty($_GET['search'])) {
|
|
$query_args['s'] = sanitize_text_field($_GET['search']);
|
|
}
|
|
|
|
if (!empty($_GET['state'])) {
|
|
$query_args['meta_query'] = array(
|
|
array(
|
|
'key' => '_VenueStateProvince',
|
|
'value' => sanitize_text_field($_GET['state']),
|
|
'compare' => '='
|
|
)
|
|
);
|
|
}
|
|
|
|
// Get venues
|
|
$venues_query = new WP_Query($query_args);
|
|
|
|
// Get total count for pagination
|
|
$total_venues = $venues_query->found_posts;
|
|
$total_pages = ceil($total_venues / $per_page);
|
|
|
|
?>
|
|
<div class="hvac-venues-filters">
|
|
<form method="get" class="hvac-filter-form">
|
|
<div class="hvac-filter-row">
|
|
<div class="hvac-filter-group">
|
|
<input type="text" name="search" placeholder="Search venues..."
|
|
value="<?php echo esc_attr($_GET['search'] ?? ''); ?>" />
|
|
</div>
|
|
<div class="hvac-filter-group">
|
|
<select name="state">
|
|
<option value="">All States</option>
|
|
<?php
|
|
// Get unique states
|
|
$states = $wpdb->get_col("
|
|
SELECT DISTINCT meta_value
|
|
FROM {$wpdb->postmeta}
|
|
WHERE meta_key = '_VenueStateProvince'
|
|
AND meta_value != ''
|
|
ORDER BY meta_value
|
|
");
|
|
|
|
foreach ($states as $state) {
|
|
printf(
|
|
'<option value="%s" %s>%s</option>',
|
|
esc_attr($state),
|
|
selected($_GET['state'] ?? '', $state, false),
|
|
esc_html($state)
|
|
);
|
|
}
|
|
?>
|
|
</select>
|
|
</div>
|
|
<div class="hvac-filter-group">
|
|
<button type="submit" class="hvac-button">Filter</button>
|
|
<a href="/trainer/venue/list/" class="hvac-button hvac-button-secondary">Clear</a>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="hvac-venues-table-wrapper">
|
|
<table class="hvac-venues-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Venue Name</th>
|
|
<th>Address</th>
|
|
<th>City</th>
|
|
<th>State</th>
|
|
<th>Phone</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php
|
|
if ($venues_query->have_posts()) {
|
|
while ($venues_query->have_posts()) {
|
|
$venues_query->the_post();
|
|
$venue_id = get_the_ID();
|
|
$is_author = (get_post_field('post_author', $venue_id) == $current_user_id);
|
|
|
|
// Get venue meta
|
|
$address = get_post_meta($venue_id, '_VenueAddress', true);
|
|
$city = get_post_meta($venue_id, '_VenueCity', true);
|
|
$state = get_post_meta($venue_id, '_VenueStateProvince', true);
|
|
$phone = get_post_meta($venue_id, '_VenuePhone', true);
|
|
?>
|
|
<tr>
|
|
<td>
|
|
<strong><?php the_title(); ?></strong>
|
|
<?php if ($is_author): ?>
|
|
<span class="hvac-badge hvac-badge-owner">Your Venue</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td><?php echo esc_html($address); ?></td>
|
|
<td><?php echo esc_html($city); ?></td>
|
|
<td><?php echo esc_html($state); ?></td>
|
|
<td><?php echo esc_html($phone); ?></td>
|
|
<td>
|
|
<?php if ($is_author): ?>
|
|
<a href="/trainer/venue/manage/?venue_id=<?php echo $venue_id; ?>"
|
|
class="hvac-button hvac-button-small">Edit</a>
|
|
<?php else: ?>
|
|
<span class="hvac-text-muted">View Only</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
<?php
|
|
}
|
|
} else {
|
|
?>
|
|
<tr>
|
|
<td colspan="6" class="hvac-no-results">No venues found.</td>
|
|
</tr>
|
|
<?php
|
|
}
|
|
wp_reset_postdata();
|
|
?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<?php if ($total_pages > 1): ?>
|
|
<div class="hvac-pagination">
|
|
<?php
|
|
echo paginate_links(array(
|
|
'base' => add_query_arg('paged', '%#%'),
|
|
'format' => '',
|
|
'current' => $page,
|
|
'total' => $total_pages,
|
|
'prev_text' => '« Previous',
|
|
'next_text' => 'Next »'
|
|
));
|
|
?>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Render venue manage form
|
|
*/
|
|
public function render_venue_manage() {
|
|
if (!is_user_logged_in()) {
|
|
return '<p>You must be logged in to view this page.</p>';
|
|
}
|
|
|
|
// Allow trainers, master trainers, or WordPress admins
|
|
if (!current_user_can('hvac_trainer') && !current_user_can('hvac_master_trainer') && !current_user_can('manage_options')) {
|
|
return '<p>You must be a trainer to view this page.</p>';
|
|
}
|
|
|
|
$venue_id = isset($_GET['venue_id']) ? intval($_GET['venue_id']) : 0;
|
|
$venue = null;
|
|
|
|
if ($venue_id) {
|
|
$venue = get_post($venue_id);
|
|
|
|
// Check if user can edit this venue
|
|
if (!$venue || $venue->post_author != get_current_user_id()) {
|
|
return '<p>You do not have permission to edit this venue.</p>';
|
|
}
|
|
}
|
|
|
|
ob_start();
|
|
?>
|
|
<div class="hvac-venue-manage">
|
|
<div class="hvac-page-header">
|
|
<h1><?php echo $venue ? 'Edit Venue' : 'Create New Venue'; ?></h1>
|
|
</div>
|
|
|
|
<?php
|
|
// Breadcrumbs are handled by page template when using WordPress pages
|
|
if (!defined('HVAC_IN_PAGE_TEMPLATE') && class_exists('HVAC_Breadcrumbs')) {
|
|
echo HVAC_Breadcrumbs::instance()->render_breadcrumbs();
|
|
}
|
|
?>
|
|
|
|
<form id="hvac-venue-form" class="hvac-form">
|
|
<?php wp_nonce_field('hvac_venue_manage', 'hvac_venue_nonce'); ?>
|
|
<input type="hidden" name="venue_id" value="<?php echo $venue_id; ?>" />
|
|
|
|
<div class="hvac-form-section">
|
|
<h3>Venue Information</h3>
|
|
|
|
<div class="hvac-form-row">
|
|
<label for="venue_name">Venue Name *</label>
|
|
<input type="text" id="venue_name" name="venue_name" required
|
|
value="<?php echo $venue ? esc_attr($venue->post_title) : ''; ?>" />
|
|
</div>
|
|
|
|
<div class="hvac-form-row">
|
|
<label for="venue_description">Description</label>
|
|
<textarea id="venue_description" name="venue_description" rows="4"><?php
|
|
echo $venue ? esc_textarea($venue->post_content) : '';
|
|
?></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="hvac-form-section">
|
|
<h3>Location Details</h3>
|
|
|
|
<div class="hvac-form-row">
|
|
<label for="venue_address">Street Address *</label>
|
|
<input type="text" id="venue_address" name="venue_address" required
|
|
value="<?php echo $venue ? esc_attr(get_post_meta($venue_id, '_VenueAddress', true)) : ''; ?>" />
|
|
</div>
|
|
|
|
<div class="hvac-form-row hvac-form-row-half">
|
|
<div>
|
|
<label for="venue_city">City *</label>
|
|
<input type="text" id="venue_city" name="venue_city" required
|
|
value="<?php echo $venue ? esc_attr(get_post_meta($venue_id, '_VenueCity', true)) : ''; ?>" />
|
|
</div>
|
|
<div>
|
|
<label for="venue_state">State/Province *</label>
|
|
<input type="text" id="venue_state" name="venue_state" required
|
|
value="<?php echo $venue ? esc_attr(get_post_meta($venue_id, '_VenueStateProvince', true)) : ''; ?>" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="hvac-form-row hvac-form-row-half">
|
|
<div>
|
|
<label for="venue_zip">Zip/Postal Code *</label>
|
|
<input type="text" id="venue_zip" name="venue_zip" required
|
|
value="<?php echo $venue ? esc_attr(get_post_meta($venue_id, '_VenueZip', true)) : ''; ?>" />
|
|
</div>
|
|
<div>
|
|
<label for="venue_country">Country *</label>
|
|
<select id="venue_country" name="venue_country" required>
|
|
<?php
|
|
$current_country = $venue ? get_post_meta($venue_id, '_VenueCountry', true) : 'United States';
|
|
$countries = array(
|
|
'United States' => 'United States',
|
|
'Canada' => 'Canada',
|
|
'United Kingdom' => 'United Kingdom',
|
|
'Australia' => 'Australia'
|
|
);
|
|
|
|
foreach ($countries as $code => $name) {
|
|
printf(
|
|
'<option value="%s" %s>%s</option>',
|
|
esc_attr($code),
|
|
selected($current_country, $code, false),
|
|
esc_html($name)
|
|
);
|
|
}
|
|
?>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="hvac-form-section">
|
|
<h3>Contact Information</h3>
|
|
|
|
<div class="hvac-form-row hvac-form-row-half">
|
|
<div>
|
|
<label for="venue_phone">Phone</label>
|
|
<input type="tel" id="venue_phone" name="venue_phone"
|
|
value="<?php echo $venue ? esc_attr(get_post_meta($venue_id, '_VenuePhone', true)) : ''; ?>" />
|
|
</div>
|
|
<div>
|
|
<label for="venue_website">Website</label>
|
|
<input type="url" id="venue_website" name="venue_website"
|
|
value="<?php echo $venue ? esc_attr(get_post_meta($venue_id, '_VenueURL', true)) : ''; ?>" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="hvac-form-actions">
|
|
<button type="submit" class="hvac-button hvac-button-primary">
|
|
<?php echo $venue ? 'Update Venue' : 'Create Venue'; ?>
|
|
</button>
|
|
<a href="/trainer/venue/list/" class="hvac-button hvac-button-secondary">Cancel</a>
|
|
|
|
<?php if ($venue): ?>
|
|
<button type="button" id="hvac-delete-venue" class="hvac-button hvac-button-danger"
|
|
data-venue-id="<?php echo $venue_id; ?>">Delete Venue</button>
|
|
<?php endif; ?>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<?php
|
|
return ob_get_clean();
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for saving venue
|
|
*/
|
|
public function ajax_save_venue() {
|
|
check_ajax_referer('hvac_venues_nonce', 'nonce');
|
|
|
|
if (!current_user_can('hvac_trainer') && !current_user_can('hvac_master_trainer') && !current_user_can('manage_options')) {
|
|
wp_send_json_error('Unauthorized');
|
|
}
|
|
|
|
$venue_id = isset($_POST['venue_id']) ? intval($_POST['venue_id']) : 0;
|
|
|
|
// If editing, check ownership
|
|
if ($venue_id) {
|
|
$venue = get_post($venue_id);
|
|
if (!$venue || $venue->post_author != get_current_user_id()) {
|
|
wp_send_json_error('You do not have permission to edit this venue.');
|
|
}
|
|
}
|
|
|
|
// Prepare venue data
|
|
$venue_data = array(
|
|
'Venue' => sanitize_text_field($_POST['venue_name']),
|
|
'Description' => wp_kses_post($_POST['venue_description']),
|
|
'Address' => sanitize_text_field($_POST['venue_address']),
|
|
'City' => sanitize_text_field($_POST['venue_city']),
|
|
'StateProvince' => sanitize_text_field($_POST['venue_state']),
|
|
'State' => sanitize_text_field($_POST['venue_state']),
|
|
'Province' => sanitize_text_field($_POST['venue_state']),
|
|
'Zip' => sanitize_text_field($_POST['venue_zip']),
|
|
'Country' => sanitize_text_field($_POST['venue_country']),
|
|
'Phone' => sanitize_text_field($_POST['venue_phone']),
|
|
'URL' => esc_url_raw($_POST['venue_website']),
|
|
'ShowMap' => true,
|
|
'ShowMapLink' => true
|
|
);
|
|
|
|
if ($venue_id) {
|
|
$venue_data['ID'] = $venue_id;
|
|
$result = function_exists('tribe_update_venue') ?
|
|
tribe_update_venue($venue_id, $venue_data) :
|
|
wp_update_post($venue_data);
|
|
} else {
|
|
$venue_data['post_status'] = 'publish';
|
|
$venue_data['post_author'] = get_current_user_id();
|
|
$result = function_exists('tribe_create_venue') ?
|
|
tribe_create_venue($venue_data) :
|
|
wp_insert_post($venue_data);
|
|
}
|
|
|
|
if (is_wp_error($result)) {
|
|
wp_send_json_error($result->get_error_message());
|
|
} else {
|
|
wp_send_json_success(array(
|
|
'message' => $venue_id ? 'Venue updated successfully.' : 'Venue created successfully.',
|
|
'venue_id' => $result
|
|
));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for deleting venue
|
|
*/
|
|
public function ajax_delete_venue() {
|
|
check_ajax_referer('hvac_venues_nonce', 'nonce');
|
|
|
|
if (!current_user_can('hvac_trainer') && !current_user_can('hvac_master_trainer') && !current_user_can('manage_options')) {
|
|
wp_send_json_error('Unauthorized');
|
|
}
|
|
|
|
$venue_id = isset($_POST['venue_id']) ? intval($_POST['venue_id']) : 0;
|
|
|
|
if (!$venue_id) {
|
|
wp_send_json_error('Invalid venue ID');
|
|
}
|
|
|
|
$venue = get_post($venue_id);
|
|
if (!$venue || $venue->post_author != get_current_user_id()) {
|
|
wp_send_json_error('You do not have permission to delete this venue.');
|
|
}
|
|
|
|
// Check if venue is being used by any events
|
|
$events_using_venue = get_posts(array(
|
|
'post_type' => class_exists('Tribe__Events__Main') ? Tribe__Events__Main::POSTTYPE : 'tribe_events',
|
|
'meta_query' => array(
|
|
array(
|
|
'key' => '_EventVenueID',
|
|
'value' => $venue_id,
|
|
'compare' => '='
|
|
)
|
|
),
|
|
'posts_per_page' => 1
|
|
));
|
|
|
|
if (!empty($events_using_venue)) {
|
|
wp_send_json_error('Cannot delete venue. It is being used by one or more events.');
|
|
}
|
|
|
|
$result = wp_trash_post($venue_id);
|
|
|
|
if ($result) {
|
|
wp_send_json_success('Venue deleted successfully.');
|
|
} else {
|
|
wp_send_json_error('Failed to delete venue.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for loading venue data
|
|
*/
|
|
public function ajax_load_venue() {
|
|
check_ajax_referer('hvac_venues_nonce', 'nonce');
|
|
|
|
if (!current_user_can('hvac_trainer') && !current_user_can('hvac_master_trainer') && !current_user_can('manage_options')) {
|
|
wp_send_json_error('Unauthorized');
|
|
}
|
|
|
|
$venue_id = isset($_GET['venue_id']) ? intval($_GET['venue_id']) : 0;
|
|
|
|
if (!$venue_id) {
|
|
wp_send_json_error('Invalid venue ID');
|
|
}
|
|
|
|
$venue = get_post($venue_id);
|
|
if (!$venue) {
|
|
wp_send_json_error('Venue not found');
|
|
}
|
|
|
|
$venue_data = array(
|
|
'id' => $venue_id,
|
|
'name' => $venue->post_title,
|
|
'description' => $venue->post_content,
|
|
'address' => get_post_meta($venue_id, '_VenueAddress', true),
|
|
'city' => get_post_meta($venue_id, '_VenueCity', true),
|
|
'state' => get_post_meta($venue_id, '_VenueStateProvince', true),
|
|
'zip' => get_post_meta($venue_id, '_VenueZip', true),
|
|
'country' => get_post_meta($venue_id, '_VenueCountry', true),
|
|
'phone' => get_post_meta($venue_id, '_VenuePhone', true),
|
|
'website' => get_post_meta($venue_id, '_VenueURL', true)
|
|
);
|
|
|
|
wp_send_json_success($venue_data);
|
|
}
|
|
}
|