'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 '

' . __('Sends notifications for new trainer registrations and ticket purchases to a Slack channel.', 'hvac-ce') . '

'; } public function slack_webhook_url_callback() { $value = get_option('hvac_slack_webhook_url', ''); echo ''; echo '

' . __('Create an Incoming Webhook in your Slack workspace and paste the URL here. Leave empty to disable.', 'hvac-ce') . '

'; if (!empty($value)) { $nonce = wp_create_nonce('hvac_test_slack_webhook'); echo '
'; echo ''; echo ''; echo '
'; ?> ' . __('Configure settings for HVAC Community Events', 'hvac-ce') . '

'; } public function notification_emails_callback() { $options = get_option('hvac_ce_options'); echo ''; echo '

' . __('Comma-separated list of emails to notify when new trainers register', 'hvac-ce') . '

'; } public function google_maps_section_callback() { echo '

' . __('Configure Google Maps API keys for maps and geocoding functionality.', 'hvac-ce') . '

'; echo '

' . __('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') . '

'; } public function maps_api_key_callback() { $value = get_option('hvac_google_maps_api_key', ''); echo ''; echo '

' . __('Browser-side API key for Google Maps JavaScript API. Should be HTTP referrer restricted to your domain.', 'hvac-ce') . '

'; } public function geocoding_api_key_callback() { $value = get_option('hvac_google_geocoding_api_key', ''); echo ''; echo '

' . __('Server-side API key for Google Geocoding API. Should be IP restricted to your server IP address.', 'hvac-ce') . '

'; // Show current status if (!empty($value)) { echo '

✓ ' . __('Geocoding API key is configured.', 'hvac-ce') . '

'; // Show batch geocode button $this->render_batch_geocode_button(); } else { echo '

⚠ ' . __('Geocoding API key not set. Venue addresses will not be geocoded for map display.', 'hvac-ce') . '

'; } } /** * 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 '
'; echo '' . __('Venue Geocoding Status', 'hvac-ce') . '
'; if ($count > 0) { echo '

' . sprintf(__('%d venues need geocoding.', 'hvac-ce'), $count) . '

'; echo ''; echo ''; } else { echo '

✓ ' . __('All venues are geocoded.', 'hvac-ce') . '

'; } echo '
'; // Render the mark as approved labs button $this->render_mark_approved_button(); // Add inline JavaScript for the batch geocode button ?> 'tribe_venue', 'posts_per_page' => -1, 'post_status' => 'publish', 'orderby' => 'title', 'order' => 'ASC' ]); $nonce = wp_create_nonce('hvac_mark_venues_approved'); echo '
'; echo '' . __('measureQuick Approved Training Labs', 'hvac-ce') . '
'; echo '

' . __('Select which venues should appear on the Find Training map as approved training labs.', 'hvac-ce') . '

'; if (empty($all_venues)) { echo '

' . __('No venues found.', 'hvac-ce') . '

'; echo '
'; 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 '

'; echo sprintf(__('%d venues total, %d approved, %d geocoded', 'hvac-ce'), count($all_venues), $approved_count, $geocoded_count); echo '

'; // Scrollable list echo '
'; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; foreach ($venue_data as $venue) { $row_style = $venue['is_approved'] ? 'background: #e7f5e7;' : ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; } echo ''; echo '
'; echo ''; echo '' . __('Venue', 'hvac-ce') . '' . __('Location', 'hvac-ce') . '' . __('Geocoded', 'hvac-ce') . '
'; echo ''; echo '' . esc_html($venue['title']) . '' . esc_html($venue['location'] ?: '—') . ''; echo $venue['has_coords'] ? '' : ''; echo '
'; echo '
'; // Save button echo ''; echo ''; echo ''; // JavaScript ?> __( 'General Settings', 'hvac-ce' ), ]; // Allow other classes to add tabs $tabs = apply_filters( 'hvac_ce_settings_tabs', $tabs ); ?>

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