Instant Slack alerts via Incoming Webhook with Block Kit rich formatting: - New trainer registrations (name, role, org, business type, photo) - Ticket purchases (purchaser, event, count, total, gateway) - Events submitted by trainers via TEC Community Events form - Events published by admins (draft/pending → publish) Settings UI with webhook URL field, validation, and test button. Non-blocking sends so Slack failures never affect user flows. Atomic add_post_meta idempotency guards prevent duplicate sends. Code reviewed by GPT-5 (Codex) — all 5 findings addressed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
605 lines
No EOL
26 KiB
PHP
605 lines
No EOL
26 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Handles plugin settings and options
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
class HVAC_Settings {
|
|
public function __construct() {
|
|
add_action('admin_menu', [$this, 'add_admin_menu']);
|
|
add_action('admin_init', [$this, 'register_settings']);
|
|
add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']);
|
|
}
|
|
|
|
public function add_admin_menu() {
|
|
// Add main menu page
|
|
add_menu_page(
|
|
__('HVAC Community Events', 'hvac-ce'),
|
|
__('HVAC Community Events', 'hvac-ce'),
|
|
'manage_options',
|
|
'hvac-community-events',
|
|
[$this, 'options_page'],
|
|
'dashicons-calendar-alt',
|
|
30
|
|
);
|
|
|
|
// Add settings submenu
|
|
add_submenu_page(
|
|
'hvac-community-events',
|
|
__('Settings', 'hvac-ce'),
|
|
__('Settings', 'hvac-ce'),
|
|
'manage_options',
|
|
'hvac-community-events',
|
|
[$this, 'options_page']
|
|
);
|
|
|
|
// Add dashboard submenu
|
|
add_submenu_page(
|
|
'hvac-community-events',
|
|
__('Dashboard', 'hvac-ce'),
|
|
__('Dashboard', 'hvac-ce'),
|
|
'manage_options',
|
|
'hvac-ce-dashboard',
|
|
[$this, 'dashboard_page']
|
|
);
|
|
|
|
// Add trainer login link submenu
|
|
$training_login_url = home_url('training-login');
|
|
add_submenu_page(
|
|
'hvac-community-events',
|
|
__('Trainer Login', 'hvac-ce'),
|
|
__('Trainer Login', 'hvac-ce'),
|
|
'manage_options',
|
|
$training_login_url,
|
|
null
|
|
);
|
|
}
|
|
|
|
public function register_settings() {
|
|
register_setting('hvac_ce_options', 'hvac_ce_options');
|
|
register_setting('hvac_ce_options', 'hvac_google_maps_api_key', [
|
|
'type' => 'string',
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
]);
|
|
register_setting('hvac_ce_options', 'hvac_google_geocoding_api_key', [
|
|
'type' => 'string',
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
]);
|
|
|
|
add_settings_section(
|
|
'hvac_ce_main',
|
|
__('HVAC Community Events Settings', 'hvac-ce'),
|
|
[$this, 'settings_section_callback'],
|
|
'hvac-ce'
|
|
);
|
|
|
|
add_settings_field(
|
|
'notification_emails',
|
|
__('Trainer Notification Emails', 'hvac-ce'),
|
|
[$this, 'notification_emails_callback'],
|
|
'hvac-ce',
|
|
'hvac_ce_main'
|
|
);
|
|
|
|
// Google Maps API Settings Section
|
|
add_settings_section(
|
|
'hvac_ce_google_maps',
|
|
__('Google Maps API Settings', 'hvac-ce'),
|
|
[$this, 'google_maps_section_callback'],
|
|
'hvac-ce'
|
|
);
|
|
|
|
add_settings_field(
|
|
'hvac_google_maps_api_key',
|
|
__('Maps JavaScript API Key', 'hvac-ce'),
|
|
[$this, 'maps_api_key_callback'],
|
|
'hvac-ce',
|
|
'hvac_ce_google_maps'
|
|
);
|
|
|
|
add_settings_field(
|
|
'hvac_google_geocoding_api_key',
|
|
__('Geocoding API Key', 'hvac-ce'),
|
|
[$this, 'geocoding_api_key_callback'],
|
|
'hvac-ce',
|
|
'hvac_ce_google_maps'
|
|
);
|
|
|
|
// Slack Integration Section
|
|
register_setting('hvac_ce_options', 'hvac_slack_webhook_url', [
|
|
'type' => 'string',
|
|
'sanitize_callback' => [$this, 'sanitize_slack_webhook_url'],
|
|
]);
|
|
|
|
add_settings_section(
|
|
'hvac_ce_slack',
|
|
__('Slack Integration', 'hvac-ce'),
|
|
[$this, 'slack_section_callback'],
|
|
'hvac-ce'
|
|
);
|
|
|
|
add_settings_field(
|
|
'hvac_slack_webhook_url',
|
|
__('Webhook URL', 'hvac-ce'),
|
|
[$this, 'slack_webhook_url_callback'],
|
|
'hvac-ce',
|
|
'hvac_ce_slack'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Sanitize Slack webhook URL — only allow hooks.slack.com
|
|
*/
|
|
public function sanitize_slack_webhook_url(mixed $value): string {
|
|
if (!is_string($value)) {
|
|
return get_option('hvac_slack_webhook_url', '');
|
|
}
|
|
$value = trim($value);
|
|
if ($value === '') {
|
|
return '';
|
|
}
|
|
|
|
$value = esc_url_raw($value);
|
|
$scheme = parse_url($value, PHP_URL_SCHEME);
|
|
$host = parse_url($value, PHP_URL_HOST);
|
|
$path = parse_url($value, PHP_URL_PATH) ?: '';
|
|
|
|
if ($scheme !== 'https' || $host !== 'hooks.slack.com' || !str_starts_with($path, '/services/')) {
|
|
add_settings_error(
|
|
'hvac_slack_webhook_url',
|
|
'invalid_url',
|
|
__('Slack webhook URL must be a valid https://hooks.slack.com/services/... URL.', 'hvac-ce'),
|
|
'error'
|
|
);
|
|
return get_option('hvac_slack_webhook_url', ''); // keep old value
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
public function slack_section_callback() {
|
|
echo '<p>' . __('Sends notifications for new trainer registrations and ticket purchases to a Slack channel.', 'hvac-ce') . '</p>';
|
|
}
|
|
|
|
public function slack_webhook_url_callback() {
|
|
$value = get_option('hvac_slack_webhook_url', '');
|
|
echo '<input type="password" name="hvac_slack_webhook_url" value="' . esc_attr($value) . '" class="regular-text" placeholder="https://hooks.slack.com/services/...">';
|
|
echo '<p class="description">' . __('Create an Incoming Webhook in your Slack workspace and paste the URL here. Leave empty to disable.', 'hvac-ce') . '</p>';
|
|
|
|
if (!empty($value)) {
|
|
$nonce = wp_create_nonce('hvac_test_slack_webhook');
|
|
echo '<div style="margin-top: 10px;">';
|
|
echo '<button type="button" id="hvac-test-slack-btn" class="button button-secondary" data-nonce="' . esc_attr($nonce) . '">';
|
|
echo __('Send Test Notification', 'hvac-ce');
|
|
echo '</button>';
|
|
echo '<span id="hvac-slack-test-status" style="margin-left: 10px;"></span>';
|
|
echo '</div>';
|
|
?>
|
|
<script type="text/javascript">
|
|
jQuery(document).ready(function($) {
|
|
$('#hvac-test-slack-btn').on('click', function() {
|
|
var $btn = $(this);
|
|
var $status = $('#hvac-slack-test-status');
|
|
var nonce = $btn.data('nonce');
|
|
|
|
$btn.prop('disabled', true).text('<?php echo esc_js(__('Sending...', 'hvac-ce')); ?>');
|
|
$status.html('');
|
|
|
|
$.ajax({
|
|
url: ajaxurl,
|
|
type: 'POST',
|
|
data: {
|
|
action: 'hvac_test_slack_webhook',
|
|
nonce: nonce
|
|
},
|
|
success: function(response) {
|
|
if (response.success) {
|
|
$status.html('<span style="color: green;">\u2713 ' + response.data.message + '</span>');
|
|
} else {
|
|
$status.html('<span style="color: red;">\u2717 ' + (response.data?.message || 'Unknown error') + '</span>');
|
|
}
|
|
$btn.prop('disabled', false).text('<?php echo esc_js(__('Send Test Notification', 'hvac-ce')); ?>');
|
|
},
|
|
error: function() {
|
|
$status.html('<span style="color: red;">\u2717 <?php echo esc_js(__('Network error. Please try again.', 'hvac-ce')); ?></span>');
|
|
$btn.prop('disabled', false).text('<?php echo esc_js(__('Send Test Notification', 'hvac-ce')); ?>');
|
|
}
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
<?php
|
|
}
|
|
}
|
|
|
|
public function settings_section_callback() {
|
|
echo '<p>' . __('Configure settings for HVAC Community Events', 'hvac-ce') . '</p>';
|
|
}
|
|
|
|
public function notification_emails_callback() {
|
|
$options = get_option('hvac_ce_options');
|
|
echo '<input type="text" name="hvac_ce_options[notification_emails]" value="' .
|
|
esc_attr($options['notification_emails'] ?? '') . '" class="regular-text">';
|
|
echo '<p class="description">' .
|
|
__('Comma-separated list of emails to notify when new trainers register', 'hvac-ce') . '</p>';
|
|
}
|
|
|
|
public function google_maps_section_callback() {
|
|
echo '<p>' . __('Configure Google Maps API keys for maps and geocoding functionality.', 'hvac-ce') . '</p>';
|
|
echo '<p class="description">' . __('You need two API keys: one for browser-side Maps JavaScript API (HTTP referrer restricted) and one for server-side Geocoding API (IP restricted).', 'hvac-ce') . '</p>';
|
|
}
|
|
|
|
public function maps_api_key_callback() {
|
|
$value = get_option('hvac_google_maps_api_key', '');
|
|
echo '<input type="text" name="hvac_google_maps_api_key" value="' . esc_attr($value) . '" class="regular-text" placeholder="AIza...">';
|
|
echo '<p class="description">' . __('Browser-side API key for Google Maps JavaScript API. Should be HTTP referrer restricted to your domain.', 'hvac-ce') . '</p>';
|
|
}
|
|
|
|
public function geocoding_api_key_callback() {
|
|
$value = get_option('hvac_google_geocoding_api_key', '');
|
|
echo '<input type="text" name="hvac_google_geocoding_api_key" value="' . esc_attr($value) . '" class="regular-text" placeholder="AIza...">';
|
|
echo '<p class="description">' . __('Server-side API key for Google Geocoding API. Should be IP restricted to your server IP address.', 'hvac-ce') . '</p>';
|
|
|
|
// Show current status
|
|
if (!empty($value)) {
|
|
echo '<p style="color: green;">✓ ' . __('Geocoding API key is configured.', 'hvac-ce') . '</p>';
|
|
|
|
// Show batch geocode button
|
|
$this->render_batch_geocode_button();
|
|
} else {
|
|
echo '<p style="color: orange;">⚠ ' . __('Geocoding API key not set. Venue addresses will not be geocoded for map display.', 'hvac-ce') . '</p>';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render batch geocode button and status
|
|
*/
|
|
private function render_batch_geocode_button() {
|
|
// Count venues without coordinates
|
|
$venues_without_coords = get_posts([
|
|
'post_type' => 'tribe_venue',
|
|
'posts_per_page' => -1,
|
|
'post_status' => 'publish',
|
|
'fields' => 'ids',
|
|
'meta_query' => [
|
|
'relation' => 'AND',
|
|
[
|
|
'key' => 'venue_latitude',
|
|
'compare' => 'NOT EXISTS'
|
|
],
|
|
[
|
|
'key' => '_VenueLat',
|
|
'compare' => 'NOT EXISTS'
|
|
]
|
|
]
|
|
]);
|
|
|
|
$count = count($venues_without_coords);
|
|
$nonce = wp_create_nonce('hvac_batch_geocode_venues');
|
|
|
|
echo '<div style="margin-top: 15px; padding: 15px; background: #f5f5f5; border-radius: 4px;">';
|
|
echo '<strong>' . __('Venue Geocoding Status', 'hvac-ce') . '</strong><br>';
|
|
|
|
if ($count > 0) {
|
|
echo '<p style="color: orange;">' . sprintf(__('%d venues need geocoding.', 'hvac-ce'), $count) . '</p>';
|
|
echo '<button type="button" id="hvac-batch-geocode-btn" class="button button-secondary" data-nonce="' . esc_attr($nonce) . '">';
|
|
echo __('Geocode All Venues', 'hvac-ce');
|
|
echo '</button>';
|
|
echo '<span id="hvac-geocode-status" style="margin-left: 10px;"></span>';
|
|
} else {
|
|
echo '<p style="color: green;">✓ ' . __('All venues are geocoded.', 'hvac-ce') . '</p>';
|
|
}
|
|
|
|
echo '</div>';
|
|
|
|
// Render the mark as approved labs button
|
|
$this->render_mark_approved_button();
|
|
|
|
// Add inline JavaScript for the batch geocode button
|
|
?>
|
|
<script type="text/javascript">
|
|
jQuery(document).ready(function($) {
|
|
$('#hvac-batch-geocode-btn').on('click', function() {
|
|
var $btn = $(this);
|
|
var $status = $('#hvac-geocode-status');
|
|
var nonce = $btn.data('nonce');
|
|
|
|
$btn.prop('disabled', true).text('<?php echo esc_js(__('Processing...', 'hvac-ce')); ?>');
|
|
$status.html('<span style="color: blue;"><?php echo esc_js(__('Geocoding venues...', 'hvac-ce')); ?></span>');
|
|
|
|
function processNextBatch() {
|
|
$.ajax({
|
|
url: ajaxurl,
|
|
type: 'POST',
|
|
data: {
|
|
action: 'hvac_batch_geocode_venues',
|
|
nonce: nonce,
|
|
limit: 10
|
|
},
|
|
success: function(response) {
|
|
if (response.success) {
|
|
var data = response.data;
|
|
var msg = '<?php echo esc_js(__('Processed', 'hvac-ce')); ?>: ' + data.processed +
|
|
', <?php echo esc_js(__('Success', 'hvac-ce')); ?>: ' + data.success +
|
|
', <?php echo esc_js(__('Remaining', 'hvac-ce')); ?>: ' + data.remaining;
|
|
$status.html('<span style="color: blue;">' + msg + '</span>');
|
|
|
|
if (data.remaining > 0) {
|
|
// Continue with next batch
|
|
setTimeout(processNextBatch, 1000);
|
|
} else {
|
|
$status.html('<span style="color: green;">✓ <?php echo esc_js(__('All venues geocoded!', 'hvac-ce')); ?></span>');
|
|
$btn.text('<?php echo esc_js(__('Done!', 'hvac-ce')); ?>');
|
|
}
|
|
} else {
|
|
$status.html('<span style="color: red;"><?php echo esc_js(__('Error', 'hvac-ce')); ?>: ' + (response.data?.message || 'Unknown error') + '</span>');
|
|
$btn.prop('disabled', false).text('<?php echo esc_js(__('Retry', 'hvac-ce')); ?>');
|
|
}
|
|
},
|
|
error: function() {
|
|
$status.html('<span style="color: red;"><?php echo esc_js(__('Network error. Please try again.', 'hvac-ce')); ?></span>');
|
|
$btn.prop('disabled', false).text('<?php echo esc_js(__('Retry', 'hvac-ce')); ?>');
|
|
}
|
|
});
|
|
}
|
|
|
|
processNextBatch();
|
|
});
|
|
});
|
|
</script>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Render scrollable list of venues with checkboxes for approved training labs
|
|
*/
|
|
private function render_mark_approved_button() {
|
|
// Get all venues
|
|
$all_venues = get_posts([
|
|
'post_type' => 'tribe_venue',
|
|
'posts_per_page' => -1,
|
|
'post_status' => 'publish',
|
|
'orderby' => 'title',
|
|
'order' => 'ASC'
|
|
]);
|
|
|
|
$nonce = wp_create_nonce('hvac_mark_venues_approved');
|
|
|
|
echo '<div style="margin-top: 15px; padding: 15px; background: #f0f7ff; border-radius: 4px; border-left: 4px solid #0073aa;">';
|
|
echo '<strong>' . __('measureQuick Approved Training Labs', 'hvac-ce') . '</strong><br>';
|
|
echo '<p class="description">' . __('Select which venues should appear on the Find Training map as approved training labs.', 'hvac-ce') . '</p>';
|
|
|
|
if (empty($all_venues)) {
|
|
echo '<p style="color: gray;">' . __('No venues found.', 'hvac-ce') . '</p>';
|
|
echo '</div>';
|
|
return;
|
|
}
|
|
|
|
// Build venue data
|
|
$venue_data = [];
|
|
$approved_count = 0;
|
|
$geocoded_count = 0;
|
|
|
|
foreach ($all_venues as $venue) {
|
|
$is_approved = has_term('mq-approved-lab', 'venue_type', $venue->ID);
|
|
$lat = get_post_meta($venue->ID, 'venue_latitude', true) ?: get_post_meta($venue->ID, '_VenueLat', true);
|
|
$lng = get_post_meta($venue->ID, 'venue_longitude', true) ?: get_post_meta($venue->ID, '_VenueLng', true);
|
|
$has_coords = !empty($lat) && !empty($lng);
|
|
|
|
$city = get_post_meta($venue->ID, '_VenueCity', true);
|
|
$state = get_post_meta($venue->ID, '_VenueState', true);
|
|
$location = trim($city . ($city && $state ? ', ' : '') . $state);
|
|
|
|
if ($is_approved) $approved_count++;
|
|
if ($has_coords) $geocoded_count++;
|
|
|
|
$venue_data[] = [
|
|
'id' => $venue->ID,
|
|
'title' => $venue->post_title,
|
|
'location' => $location,
|
|
'is_approved' => $is_approved,
|
|
'has_coords' => $has_coords
|
|
];
|
|
}
|
|
|
|
// Summary
|
|
echo '<p style="margin: 10px 0;">';
|
|
echo sprintf(__('<strong>%d</strong> venues total, <strong>%d</strong> approved, <strong>%d</strong> geocoded', 'hvac-ce'),
|
|
count($all_venues), $approved_count, $geocoded_count);
|
|
echo '</p>';
|
|
|
|
// Scrollable list
|
|
echo '<div style="max-height: 300px; overflow-y: auto; border: 1px solid #ddd; background: #fff; padding: 10px; margin: 10px 0;">';
|
|
echo '<table style="width: 100%; border-collapse: collapse;">';
|
|
echo '<thead style="position: sticky; top: 0; background: #f5f5f5;">';
|
|
echo '<tr>';
|
|
echo '<th style="padding: 8px; text-align: left; border-bottom: 2px solid #ddd; width: 30px;">';
|
|
echo '<input type="checkbox" id="hvac-select-all-venues" title="' . esc_attr__('Select/Deselect All', 'hvac-ce') . '">';
|
|
echo '</th>';
|
|
echo '<th style="padding: 8px; text-align: left; border-bottom: 2px solid #ddd;">' . __('Venue', 'hvac-ce') . '</th>';
|
|
echo '<th style="padding: 8px; text-align: left; border-bottom: 2px solid #ddd;">' . __('Location', 'hvac-ce') . '</th>';
|
|
echo '<th style="padding: 8px; text-align: center; border-bottom: 2px solid #ddd;">' . __('Geocoded', 'hvac-ce') . '</th>';
|
|
echo '</tr>';
|
|
echo '</thead>';
|
|
echo '<tbody>';
|
|
|
|
foreach ($venue_data as $venue) {
|
|
$row_style = $venue['is_approved'] ? 'background: #e7f5e7;' : '';
|
|
echo '<tr style="' . $row_style . '">';
|
|
echo '<td style="padding: 6px 8px; border-bottom: 1px solid #eee;">';
|
|
echo '<input type="checkbox" class="hvac-venue-checkbox" value="' . esc_attr($venue['id']) . '"' .
|
|
($venue['is_approved'] ? ' checked' : '') . '>';
|
|
echo '</td>';
|
|
echo '<td style="padding: 6px 8px; border-bottom: 1px solid #eee;">' . esc_html($venue['title']) . '</td>';
|
|
echo '<td style="padding: 6px 8px; border-bottom: 1px solid #eee; color: #666;">' . esc_html($venue['location'] ?: '—') . '</td>';
|
|
echo '<td style="padding: 6px 8px; border-bottom: 1px solid #eee; text-align: center;">';
|
|
echo $venue['has_coords'] ? '<span style="color: green;">✓</span>' : '<span style="color: #ccc;">—</span>';
|
|
echo '</td>';
|
|
echo '</tr>';
|
|
}
|
|
|
|
echo '</tbody>';
|
|
echo '</table>';
|
|
echo '</div>';
|
|
|
|
// Save button
|
|
echo '<button type="button" id="hvac-save-approved-labs" class="button button-primary" data-nonce="' . esc_attr($nonce) . '">';
|
|
echo __('Save Approved Labs', 'hvac-ce');
|
|
echo '</button>';
|
|
echo '<span id="hvac-approved-status" style="margin-left: 10px;"></span>';
|
|
|
|
echo '</div>';
|
|
|
|
// JavaScript
|
|
?>
|
|
<script type="text/javascript">
|
|
jQuery(document).ready(function($) {
|
|
// Select all checkbox
|
|
$('#hvac-select-all-venues').on('change', function() {
|
|
$('.hvac-venue-checkbox').prop('checked', $(this).is(':checked'));
|
|
});
|
|
|
|
// Update select-all state when individual checkboxes change
|
|
$('.hvac-venue-checkbox').on('change', function() {
|
|
var total = $('.hvac-venue-checkbox').length;
|
|
var checked = $('.hvac-venue-checkbox:checked').length;
|
|
$('#hvac-select-all-venues').prop('checked', total === checked);
|
|
$('#hvac-select-all-venues').prop('indeterminate', checked > 0 && checked < total);
|
|
});
|
|
|
|
// Initialize select-all state
|
|
(function() {
|
|
var total = $('.hvac-venue-checkbox').length;
|
|
var checked = $('.hvac-venue-checkbox:checked').length;
|
|
$('#hvac-select-all-venues').prop('checked', total === checked);
|
|
$('#hvac-select-all-venues').prop('indeterminate', checked > 0 && checked < total);
|
|
})();
|
|
|
|
// Save button
|
|
$('#hvac-save-approved-labs').on('click', function() {
|
|
var $btn = $(this);
|
|
var $status = $('#hvac-approved-status');
|
|
var nonce = $btn.data('nonce');
|
|
|
|
// Collect selected venue IDs
|
|
var selectedIds = [];
|
|
$('.hvac-venue-checkbox:checked').each(function() {
|
|
selectedIds.push($(this).val());
|
|
});
|
|
|
|
$btn.prop('disabled', true).text('<?php echo esc_js(__('Saving...', 'hvac-ce')); ?>');
|
|
$status.html('<span style="color: blue;"><?php echo esc_js(__('Updating venues...', 'hvac-ce')); ?></span>');
|
|
|
|
$.ajax({
|
|
url: ajaxurl,
|
|
type: 'POST',
|
|
data: {
|
|
action: 'hvac_update_approved_labs',
|
|
nonce: nonce,
|
|
venue_ids: selectedIds
|
|
},
|
|
success: function(response) {
|
|
if (response.success) {
|
|
var data = response.data;
|
|
var msg = '<?php echo esc_js(__('Saved!', 'hvac-ce')); ?> ' + data.approved_count + ' <?php echo esc_js(__('approved labs', 'hvac-ce')); ?>';
|
|
$status.html('<span style="color: green;">✓ ' + msg + '</span>');
|
|
$btn.prop('disabled', false).text('<?php echo esc_js(__('Save Approved Labs', 'hvac-ce')); ?>');
|
|
|
|
// Update row highlighting
|
|
$('.hvac-venue-checkbox').each(function() {
|
|
var $row = $(this).closest('tr');
|
|
if ($(this).is(':checked')) {
|
|
$row.css('background', '#e7f5e7');
|
|
} else {
|
|
$row.css('background', '');
|
|
}
|
|
});
|
|
} else {
|
|
$status.html('<span style="color: red;"><?php echo esc_js(__('Error', 'hvac-ce')); ?>: ' + (response.data?.message || 'Unknown error') + '</span>');
|
|
$btn.prop('disabled', false).text('<?php echo esc_js(__('Save Approved Labs', 'hvac-ce')); ?>');
|
|
}
|
|
},
|
|
error: function() {
|
|
$status.html('<span style="color: red;"><?php echo esc_js(__('Network error. Please try again.', 'hvac-ce')); ?></span>');
|
|
$btn.prop('disabled', false).text('<?php echo esc_js(__('Save Approved Labs', 'hvac-ce')); ?>');
|
|
}
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
<?php
|
|
}
|
|
|
|
public function options_page() {
|
|
$active_tab = isset( $_GET['tab'] ) ? sanitize_text_field( $_GET['tab'] ) : 'general';
|
|
|
|
$tabs = [
|
|
'general' => __( 'General Settings', 'hvac-ce' ),
|
|
];
|
|
|
|
// Allow other classes to add tabs
|
|
$tabs = apply_filters( 'hvac_ce_settings_tabs', $tabs );
|
|
?>
|
|
<div class="wrap">
|
|
<h1><?php esc_html_e('HVAC Community Events Settings', 'hvac-ce'); ?></h1>
|
|
|
|
<h2 class="nav-tab-wrapper">
|
|
<?php foreach ( $tabs as $tab_key => $tab_label ): ?>
|
|
<a href="?page=hvac-community-events&tab=<?php echo esc_attr( $tab_key ); ?>"
|
|
class="nav-tab <?php echo $active_tab === $tab_key ? 'nav-tab-active' : ''; ?>">
|
|
<?php echo esc_html( $tab_label ); ?>
|
|
</a>
|
|
<?php endforeach; ?>
|
|
</h2>
|
|
|
|
<?php if ( $active_tab === 'general' ): ?>
|
|
<form method="post" action="options.php">
|
|
<?php
|
|
settings_fields('hvac_ce_options');
|
|
do_settings_sections('hvac-ce');
|
|
submit_button();
|
|
?>
|
|
</form>
|
|
<?php else: ?>
|
|
<?php do_action( 'hvac_ce_settings_content', $active_tab ); ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Dashboard page callback
|
|
*/
|
|
public function dashboard_page() {
|
|
// Load the admin dashboard class
|
|
if (!class_exists('HVAC_Admin_Dashboard')) {
|
|
require_once HVAC_PLUGIN_DIR . 'includes/admin/class-admin-dashboard.php';
|
|
}
|
|
|
|
$dashboard = new HVAC_Admin_Dashboard();
|
|
$dashboard->render_page();
|
|
}
|
|
|
|
/**
|
|
* Enqueue admin scripts and styles
|
|
*/
|
|
public function enqueue_admin_scripts($hook) {
|
|
// Only load on HVAC admin pages
|
|
if (strpos($hook, 'hvac-community-events') !== false) {
|
|
// Add inline script to make trainer login link open in new tab
|
|
$script = "
|
|
jQuery(document).ready(function($) {
|
|
// Find the trainer login menu link and make it open in new tab
|
|
$('a[href*=\"training-login\"]').attr('target', '_blank');
|
|
});
|
|
";
|
|
wp_add_inline_script('jquery', $script);
|
|
}
|
|
}
|
|
}
|