'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 '' . __('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 '' . sprintf(__('%d venues need geocoding.', 'hvac-ce'), $count) . '
'; echo ''; echo ''; } else { echo '✓ ' . __('All venues are geocoded.', '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 ''; 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 '' . __('Venue', 'hvac-ce') . ' | '; echo '' . __('Location', 'hvac-ce') . ' | '; echo '' . __('Geocoded', 'hvac-ce') . ' | '; echo '
|---|---|---|---|
| '; echo ''; echo ' | '; echo '' . esc_html($venue['title']) . ' | '; echo '' . esc_html($venue['location'] ?: '—') . ' | '; echo ''; echo $venue['has_coords'] ? '✓' : '—'; echo ' | '; echo '