feat: Staging email filter, venue geocoding, MapGeo improvements, trainers overview
Accumulated changes from previous sessions (Feb 9-20): - Staging email filter to prevent test emails reaching real users - Version bump to 2.2.0 in plugin header - Venue geocoding enhancements and batch processing - Find Training page improvements (tab content, map data) - MapGeo integration hardening for Find a Trainer - Master trainers overview table improvements - AJAX handler additions for venue categories - SVG marker icon tweaks (trainer, venue) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
25bf5d98e1
commit
ca928bfffb
9 changed files with 453 additions and 77 deletions
Binary file not shown.
|
Before Width: | Height: | Size: 339 B After Width: | Height: | Size: 341 B |
Binary file not shown.
|
Before Width: | Height: | Size: 360 B After Width: | Height: | Size: 362 B |
|
|
@ -3,7 +3,7 @@
|
||||||
* Plugin Name: HVAC Community Events
|
* Plugin Name: HVAC Community Events
|
||||||
* Plugin URI: https://upskillhvac.com
|
* Plugin URI: https://upskillhvac.com
|
||||||
* Description: Custom plugin for HVAC trainer event management system
|
* Description: Custom plugin for HVAC trainer event management system
|
||||||
* Version: 2.1.7
|
* Version: 2.2.0
|
||||||
* Author: Upskill HVAC
|
* Author: Upskill HVAC
|
||||||
* Author URI: https://upskillhvac.com
|
* Author URI: https://upskillhvac.com
|
||||||
* License: GPL-2.0+
|
* License: GPL-2.0+
|
||||||
|
|
@ -17,6 +17,50 @@ if (!defined('ABSPATH')) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Staging Email Filter
|
||||||
|
* Prevents emails from being sent to anyone except allowed addresses on staging.
|
||||||
|
* This protects real users from receiving test emails during development.
|
||||||
|
*/
|
||||||
|
function hvac_is_staging_environment() {
|
||||||
|
$host = isset( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : '';
|
||||||
|
$staging_indicators = array( 'staging', 'upskill-staging', 'localhost', '127.0.0.1' );
|
||||||
|
|
||||||
|
foreach ( $staging_indicators as $indicator ) {
|
||||||
|
if ( stripos( $host, $indicator ) !== false ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hvac_staging_email_filter( $args ) {
|
||||||
|
if ( ! hvac_is_staging_environment() ) {
|
||||||
|
return $args;
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowed_emails = array( 'ben@tealmaker.com', 'ben@measurequick.com' );
|
||||||
|
$to = $args['to'];
|
||||||
|
|
||||||
|
// Extract email address
|
||||||
|
$email_address = is_array( $to ) ? ( isset( $to[0] ) ? $to[0] : '' ) : $to;
|
||||||
|
if ( preg_match( '/<([^>]+)>/', $email_address, $matches ) ) {
|
||||||
|
$email_address = $matches[1];
|
||||||
|
}
|
||||||
|
$email_address = trim( strtolower( $email_address ) );
|
||||||
|
|
||||||
|
if ( ! in_array( $email_address, $allowed_emails, true ) ) {
|
||||||
|
error_log( sprintf( '[HVAC Staging] Blocked email to: %s | Subject: %s',
|
||||||
|
is_array( $to ) ? implode( ', ', $to ) : $to, $args['subject'] ) );
|
||||||
|
$args['to'] = '';
|
||||||
|
return $args;
|
||||||
|
}
|
||||||
|
|
||||||
|
$args['subject'] = '[STAGING] ' . $args['subject'];
|
||||||
|
return $args;
|
||||||
|
}
|
||||||
|
add_filter( 'wp_mail', 'hvac_staging_email_filter', 1 );
|
||||||
|
|
||||||
// Load the main plugin class
|
// Load the main plugin class
|
||||||
require_once plugin_dir_path(__FILE__) . 'includes/class-hvac-plugin.php';
|
require_once plugin_dir_path(__FILE__) . 'includes/class-hvac-plugin.php';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1046,6 +1046,15 @@ class HVAC_Ajax_Handlers {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify reCAPTCHA
|
||||||
|
if (class_exists('HVAC_Recaptcha')) {
|
||||||
|
$recaptcha_response = sanitize_text_field($_POST['g-recaptcha-response'] ?? '');
|
||||||
|
if (!HVAC_Recaptcha::instance()->verify_response($recaptcha_response)) {
|
||||||
|
wp_send_json_error(['message' => 'CAPTCHA verification failed. Please try again.'], 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Rate limiting - max 5 submissions per IP per hour
|
// Rate limiting - max 5 submissions per IP per hour
|
||||||
$ip = $this->get_client_ip();
|
$ip = $this->get_client_ip();
|
||||||
$rate_key = 'hvac_contact_rate_' . md5($ip);
|
$rate_key = 'hvac_contact_rate_' . md5($ip);
|
||||||
|
|
@ -1202,6 +1211,15 @@ class HVAC_Ajax_Handlers {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify reCAPTCHA
|
||||||
|
if (class_exists('HVAC_Recaptcha')) {
|
||||||
|
$recaptcha_response = sanitize_text_field($_POST['g-recaptcha-response'] ?? '');
|
||||||
|
if (!HVAC_Recaptcha::instance()->verify_response($recaptcha_response)) {
|
||||||
|
wp_send_json_error(['message' => 'CAPTCHA verification failed. Please try again.'], 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Rate limiting - max 5 submissions per IP per hour
|
// Rate limiting - max 5 submissions per IP per hour
|
||||||
$ip = $this->get_client_ip();
|
$ip = $this->get_client_ip();
|
||||||
$rate_key = 'hvac_venue_contact_rate_' . md5($ip);
|
$rate_key = 'hvac_venue_contact_rate_' . md5($ip);
|
||||||
|
|
|
||||||
|
|
@ -291,6 +291,8 @@ class HVAC_Master_Trainers_Overview {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AJAX handler for filtering trainers
|
* AJAX handler for filtering trainers
|
||||||
|
*
|
||||||
|
* Uses get_trainers_table_data() from dashboard data class for reliable trainer listing
|
||||||
*/
|
*/
|
||||||
public function ajax_filter_trainers() {
|
public function ajax_filter_trainers() {
|
||||||
// Verify nonce
|
// Verify nonce
|
||||||
|
|
@ -306,7 +308,6 @@ class HVAC_Master_Trainers_Overview {
|
||||||
// Get filter parameters
|
// Get filter parameters
|
||||||
$args = array(
|
$args = array(
|
||||||
'status' => isset( $_POST['status'] ) ? sanitize_text_field( $_POST['status'] ) : 'all',
|
'status' => isset( $_POST['status'] ) ? sanitize_text_field( $_POST['status'] ) : 'all',
|
||||||
'region' => isset( $_POST['region'] ) ? sanitize_text_field( $_POST['region'] ) : '',
|
|
||||||
'search' => isset( $_POST['search'] ) ? sanitize_text_field( $_POST['search'] ) : '',
|
'search' => isset( $_POST['search'] ) ? sanitize_text_field( $_POST['search'] ) : '',
|
||||||
'page' => isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 1,
|
'page' => isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 1,
|
||||||
'per_page' => isset( $_POST['per_page'] ) ? absint( $_POST['per_page'] ) : 20,
|
'per_page' => isset( $_POST['per_page'] ) ? absint( $_POST['per_page'] ) : 20,
|
||||||
|
|
@ -314,12 +315,21 @@ class HVAC_Master_Trainers_Overview {
|
||||||
'order' => isset( $_POST['order'] ) ? sanitize_text_field( $_POST['order'] ) : 'ASC',
|
'order' => isset( $_POST['order'] ) ? sanitize_text_field( $_POST['order'] ) : 'ASC',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get trainers data
|
// Use the reliable get_trainers_table_data method (same as Master Dashboard)
|
||||||
if ( $this->dashboard_data ) {
|
if ( $this->dashboard_data ) {
|
||||||
$trainer_stats = $this->dashboard_data->get_trainer_statistics();
|
$trainer_data = $this->dashboard_data->get_trainers_table_data( $args );
|
||||||
|
$trainers = $trainer_data['trainers'];
|
||||||
|
|
||||||
// Format trainers for display
|
// Apply region filter if specified (not handled by get_trainers_table_data)
|
||||||
$formatted_trainers = $this->format_trainers_for_display( $trainer_stats['trainer_data'], $args );
|
$region_filter = isset( $_POST['region'] ) ? sanitize_text_field( $_POST['region'] ) : '';
|
||||||
|
if ( ! empty( $region_filter ) ) {
|
||||||
|
$trainers = array_filter( $trainers, function( $trainer ) use ( $region_filter ) {
|
||||||
|
$user_meta = get_user_meta( $trainer['id'] );
|
||||||
|
$user_state = isset( $user_meta['billing_state'][0] ) ? $user_meta['billing_state'][0] : '';
|
||||||
|
return $user_state === $region_filter;
|
||||||
|
} );
|
||||||
|
$trainers = array_values( $trainers ); // Re-index array
|
||||||
|
}
|
||||||
|
|
||||||
// Generate HTML table
|
// Generate HTML table
|
||||||
ob_start();
|
ob_start();
|
||||||
|
|
@ -329,20 +339,20 @@ class HVAC_Master_Trainers_Overview {
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Email</th>
|
<th>Email</th>
|
||||||
<th>Location</th>
|
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th>Total Events</th>
|
<th>Total Events</th>
|
||||||
|
<th>Revenue</th>
|
||||||
<th>Registered</th>
|
<th>Registered</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php if ( empty( $formatted_trainers ) ) : ?>
|
<?php if ( empty( $trainers ) ) : ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="7" class="hvac-no-results">No trainers found matching your criteria.</td>
|
<td colspan="7" class="hvac-no-results">No trainers found matching your criteria.</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php else : ?>
|
<?php else : ?>
|
||||||
<?php foreach ( $formatted_trainers as $trainer ) : ?>
|
<?php foreach ( $trainers as $trainer ) : ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="hvac-trainer-name">
|
<td class="hvac-trainer-name">
|
||||||
<strong><?php echo esc_html( $trainer['name'] ); ?></strong>
|
<strong><?php echo esc_html( $trainer['name'] ); ?></strong>
|
||||||
|
|
@ -352,22 +362,22 @@ class HVAC_Master_Trainers_Overview {
|
||||||
<?php echo esc_html( $trainer['email'] ); ?>
|
<?php echo esc_html( $trainer['email'] ); ?>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="hvac-trainer-location">
|
|
||||||
<?php echo esc_html( $trainer['location'] ); ?>
|
|
||||||
</td>
|
|
||||||
<td class="hvac-trainer-status">
|
<td class="hvac-trainer-status">
|
||||||
<span class="hvac-status-badge <?php echo esc_attr( $trainer['status_class'] ); ?>">
|
<span class="hvac-status-badge hvac-status-<?php echo esc_attr( $trainer['status'] ); ?>">
|
||||||
<?php echo esc_html( $trainer['status'] ); ?>
|
<?php echo esc_html( $trainer['status_label'] ); ?>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="hvac-trainer-events">
|
<td class="hvac-trainer-events">
|
||||||
<?php echo esc_html( $trainer['total_events'] ); ?>
|
<?php echo esc_html( $trainer['total_events'] ); ?>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="hvac-trainer-revenue">
|
||||||
|
$<?php echo esc_html( number_format( $trainer['revenue'], 2 ) ); ?>
|
||||||
|
</td>
|
||||||
<td class="hvac-trainer-registered">
|
<td class="hvac-trainer-registered">
|
||||||
<?php echo esc_html( $trainer['registered'] ); ?>
|
<?php echo esc_html( $trainer['registration_date'] ); ?>
|
||||||
</td>
|
</td>
|
||||||
<td class="hvac-trainer-actions">
|
<td class="hvac-trainer-actions">
|
||||||
<a href="<?php echo esc_url( $trainer['profile_link'] ); ?>"
|
<a href="<?php echo esc_url( home_url( '/master-trainer/trainer-profile/' . $trainer['id'] . '/' ) ); ?>"
|
||||||
class="hvac-btn hvac-btn-sm hvac-btn-primary hvac-view-trainer"
|
class="hvac-btn hvac-btn-sm hvac-btn-primary hvac-view-trainer"
|
||||||
data-trainer-id="<?php echo esc_attr( $trainer['id'] ); ?>">
|
data-trainer-id="<?php echo esc_attr( $trainer['id'] ); ?>">
|
||||||
View Profile
|
View Profile
|
||||||
|
|
@ -379,14 +389,18 @@ class HVAC_Master_Trainers_Overview {
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="hvac-trainers-count">
|
<div class="hvac-trainers-count">
|
||||||
Showing <?php echo esc_html( count( $formatted_trainers ) ); ?> trainer(s)
|
Showing <?php echo esc_html( count( $trainers ) ); ?> trainer(s)
|
||||||
|
<?php if ( ! empty( $trainer_data['pagination'] ) ) : ?>
|
||||||
|
of <?php echo esc_html( $trainer_data['pagination']['total_items'] ); ?> total
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<?php
|
<?php
|
||||||
$html = ob_get_clean();
|
$html = ob_get_clean();
|
||||||
|
|
||||||
wp_send_json_success( array(
|
wp_send_json_success( array(
|
||||||
'html' => $html,
|
'html' => $html,
|
||||||
'total_found' => count( $formatted_trainers )
|
'total_found' => count( $trainers ),
|
||||||
|
'pagination' => $trainer_data['pagination']
|
||||||
) );
|
) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -514,16 +528,35 @@ class HVAC_Master_Trainers_Overview {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Count trainers by status
|
* Count trainers by status
|
||||||
|
*
|
||||||
|
* Uses HVAC_Trainer_Status for dynamic status calculation (active/inactive based on event activity)
|
||||||
*/
|
*/
|
||||||
private function count_trainers_by_status( $status ) {
|
private function count_trainers_by_status( $status ) {
|
||||||
|
// Load trainer status class for dynamic status calculation
|
||||||
|
if ( ! class_exists( 'HVAC_Trainer_Status' ) ) {
|
||||||
|
require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-trainer-status.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all trainers (both roles)
|
||||||
$users = get_users( array(
|
$users = get_users( array(
|
||||||
'role' => 'hvac_trainer',
|
'role__in' => array( 'hvac_trainer', 'hvac_master_trainer' ),
|
||||||
'meta_key' => 'hvac_trainer_status',
|
'fields' => 'ID'
|
||||||
'meta_value' => $status,
|
|
||||||
'count_total' => true
|
|
||||||
) );
|
) );
|
||||||
|
|
||||||
return is_array( $users ) ? count( $users ) : 0;
|
if ( empty( $users ) ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count by dynamic status
|
||||||
|
$count = 0;
|
||||||
|
foreach ( $users as $user_id ) {
|
||||||
|
$user_status = HVAC_Trainer_Status::get_trainer_status( $user_id );
|
||||||
|
if ( $user_status === $status ) {
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $count;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,16 @@ class HVAC_Venue_Categories {
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
private function __construct() {
|
private function __construct() {
|
||||||
|
// If init has already fired (we're being initialized late), register immediately
|
||||||
|
// This handles the case where the class is instantiated during 'init' priority >= 5
|
||||||
|
if (did_action('init')) {
|
||||||
|
$this->register_taxonomies();
|
||||||
|
$this->create_default_terms();
|
||||||
|
} else {
|
||||||
add_action('init', [$this, 'register_taxonomies'], 5);
|
add_action('init', [$this, 'register_taxonomies'], 5);
|
||||||
add_action('init', [$this, 'create_default_terms'], 10);
|
add_action('init', [$this, 'create_default_terms'], 10);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register venue taxonomies
|
* Register venue taxonomies
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,11 @@ class HVAC_MapGeo_Integration {
|
||||||
add_action('wp_ajax_hvac_search_trainers', [$this, 'ajax_search_trainers']);
|
add_action('wp_ajax_hvac_search_trainers', [$this, 'ajax_search_trainers']);
|
||||||
add_action('wp_ajax_nopriv_hvac_search_trainers', [$this, 'ajax_search_trainers']);
|
add_action('wp_ajax_nopriv_hvac_search_trainers', [$this, 'ajax_search_trainers']);
|
||||||
|
|
||||||
// Add JavaScript to handle MapGeo marker clicks - Priority 0 to ensure interceptor runs before localization
|
// Add JavaScript to handle MapGeo marker clicks - MUST run in wp_head BEFORE shortcode renders
|
||||||
|
// The IGM plugin outputs iMapsData during shortcode execution, so interceptor must be installed first
|
||||||
|
add_action('wp_head', [$this, 'add_mapgeo_interceptor'], 1);
|
||||||
|
|
||||||
|
// Add click handlers in footer (these run after map initializes)
|
||||||
add_action('wp_footer', [$this, 'add_mapgeo_click_handlers'], 0);
|
add_action('wp_footer', [$this, 'add_mapgeo_click_handlers'], 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -400,8 +404,84 @@ class HVAC_MapGeo_Integration {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add JavaScript to handle MapGeo custom click actions
|
* Add JavaScript interceptor in wp_head BEFORE shortcode renders
|
||||||
|
* This MUST run before IGM plugin outputs iMapsData
|
||||||
*/
|
*/
|
||||||
|
public function add_mapgeo_interceptor() {
|
||||||
|
// Only add on find trainer page
|
||||||
|
if (!is_page() || get_post_field('post_name') !== 'find-a-trainer') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
<script type="text/javascript">
|
||||||
|
// Strategy H (v2): Multi-stage healing for IGM coordinate corruption
|
||||||
|
// The IGM plugin corrupts longitude AFTER initial assignment, so we need multiple healing passes
|
||||||
|
(function() {
|
||||||
|
// Prevent double-installation
|
||||||
|
if (window._hvacMapInterceptorInstalled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window._hvacMapInterceptorInstalled = true;
|
||||||
|
|
||||||
|
// Healing function that can be called multiple times
|
||||||
|
window._hvacHealMapMarkers = function(source) {
|
||||||
|
if (typeof window.iMapsData === 'undefined' || !window.iMapsData) return 0;
|
||||||
|
var data = window.iMapsData;
|
||||||
|
if (!data.data || !data.data[0]) return 0;
|
||||||
|
|
||||||
|
var markerTypes = ['roundMarkers', 'iconMarkers', 'markers', 'customMarkers'];
|
||||||
|
var totalHealed = 0;
|
||||||
|
|
||||||
|
markerTypes.forEach(function(markerType) {
|
||||||
|
if (data.data[0][markerType] && Array.isArray(data.data[0][markerType])) {
|
||||||
|
data.data[0][markerType].forEach(function(m) {
|
||||||
|
// Detect corruption: latitude === longitude but backup values differ
|
||||||
|
if (m.lat && m.lng && m.latitude == m.longitude && parseFloat(m.lat) != parseFloat(m.lng)) {
|
||||||
|
m.latitude = parseFloat(m.lat);
|
||||||
|
m.longitude = parseFloat(m.lng);
|
||||||
|
totalHealed++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (totalHealed > 0) {
|
||||||
|
console.log('✅ HVAC MapGeo Healer [' + source + ']: Fixed ' + totalHealed + ' corrupted coordinates.');
|
||||||
|
}
|
||||||
|
return totalHealed;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Schedule multiple healing passes to catch post-assignment corruption
|
||||||
|
var healingAttempts = 0;
|
||||||
|
var maxAttempts = 10;
|
||||||
|
var healingInterval = setInterval(function() {
|
||||||
|
healingAttempts++;
|
||||||
|
var healed = window._hvacHealMapMarkers('interval-' + healingAttempts);
|
||||||
|
|
||||||
|
// Stop after max attempts or if we've healed and no more corruption
|
||||||
|
if (healingAttempts >= maxAttempts) {
|
||||||
|
clearInterval(healingInterval);
|
||||||
|
console.log('🛡️ HVAC MapGeo Healer: Completed ' + healingAttempts + ' healing passes.');
|
||||||
|
}
|
||||||
|
}, 100); // Check every 100ms
|
||||||
|
|
||||||
|
// Also heal on DOMContentLoaded and window load
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
setTimeout(function() { window._hvacHealMapMarkers('DOMContentLoaded'); }, 50);
|
||||||
|
setTimeout(function() { window._hvacHealMapMarkers('DOMContentLoaded+500'); }, 500);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
setTimeout(function() { window._hvacHealMapMarkers('window.load'); }, 100);
|
||||||
|
setTimeout(function() { window._hvacHealMapMarkers('window.load+1000'); }, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('🛡️ HVAC MapGeo Healer installed with multi-stage healing');
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add JavaScript to handle MapGeo custom click actions
|
* Add JavaScript to handle MapGeo custom click actions
|
||||||
|
|
@ -414,40 +494,6 @@ class HVAC_MapGeo_Integration {
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
// Strategy H: Intercept iMapsData assignment to fix corruption
|
|
||||||
(function() {
|
|
||||||
var _data = undefined;
|
|
||||||
|
|
||||||
// Only intercept if not defined yet
|
|
||||||
if (typeof iMapsData === 'undefined') {
|
|
||||||
Object.defineProperty(window, 'iMapsData', {
|
|
||||||
get: function() { return _data; },
|
|
||||||
set: function(val) {
|
|
||||||
// Fix corruption immediately upon assignment
|
|
||||||
if(val && val.data && val.data[0]) {
|
|
||||||
// Handle roundMarkers
|
|
||||||
if (val.data[0].roundMarkers) {
|
|
||||||
var healedCount = 0;
|
|
||||||
val.data[0].roundMarkers.forEach(function(m) {
|
|
||||||
// Restore from safe lat/lng keys if corruption detected
|
|
||||||
if(m.lat && m.lng && m.latitude == m.longitude) {
|
|
||||||
m.latitude = m.lat;
|
|
||||||
m.longitude = m.lng;
|
|
||||||
healedCount++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if(healedCount > 0) {
|
|
||||||
console.log('✅ HVAC MapGeo Interceptor: Healed ' + healedCount + ' corrupted markers instantly.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_data = val;
|
|
||||||
},
|
|
||||||
configurable: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
jQuery(document).ready(function($) {
|
jQuery(document).ready(function($) {
|
||||||
// Disable console logging in production
|
// Disable console logging in production
|
||||||
var isProduction = window.location.hostname === 'upskillhvac.com';
|
var isProduction = window.location.hostname === 'upskillhvac.com';
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,21 @@ class HVAC_Find_Training_Page {
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function is_find_training_page(): bool {
|
public function is_find_training_page(): bool {
|
||||||
return is_page($this->page_slug) || is_page(get_option('hvac_find_training_page_id'));
|
if (is_page($this->page_slug)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$page_id = get_option('hvac_find_training_page_id');
|
||||||
|
return $page_id && is_page($page_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if Google Maps API key is configured
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function is_api_key_configured(): bool {
|
||||||
|
return !empty($this->api_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -158,6 +172,8 @@ class HVAC_Find_Training_Page {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$api_key_configured = !empty($this->api_key);
|
||||||
|
|
||||||
// Enqueue CSS
|
// Enqueue CSS
|
||||||
wp_enqueue_style(
|
wp_enqueue_style(
|
||||||
'hvac-find-training',
|
'hvac-find-training',
|
||||||
|
|
@ -166,8 +182,16 @@ class HVAC_Find_Training_Page {
|
||||||
HVAC_VERSION
|
HVAC_VERSION
|
||||||
);
|
);
|
||||||
|
|
||||||
// Enqueue Google Maps API with MarkerClusterer
|
// Enqueue Google reCAPTCHA for contact forms
|
||||||
if (!empty($this->api_key)) {
|
if (class_exists('HVAC_Recaptcha')) {
|
||||||
|
HVAC_Recaptcha::instance()->enqueue_script();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build script dependencies
|
||||||
|
$map_script_deps = ['jquery'];
|
||||||
|
|
||||||
|
// Enqueue Google Maps API with MarkerClusterer only if API key is configured
|
||||||
|
if ($api_key_configured) {
|
||||||
wp_enqueue_script(
|
wp_enqueue_script(
|
||||||
'google-maps-api',
|
'google-maps-api',
|
||||||
'https://maps.googleapis.com/maps/api/js?key=' . esc_attr($this->api_key) . '&libraries=places&callback=Function.prototype',
|
'https://maps.googleapis.com/maps/api/js?key=' . esc_attr($this->api_key) . '&libraries=places&callback=Function.prototype',
|
||||||
|
|
@ -184,13 +208,16 @@ class HVAC_Find_Training_Page {
|
||||||
'2.5.3',
|
'2.5.3',
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$map_script_deps[] = 'google-maps-api';
|
||||||
|
$map_script_deps[] = 'google-maps-markerclusterer';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enqueue main map JavaScript
|
// Enqueue main map JavaScript (always load for directory functionality)
|
||||||
wp_enqueue_script(
|
wp_enqueue_script(
|
||||||
'hvac-find-training-map',
|
'hvac-find-training-map',
|
||||||
HVAC_PLUGIN_URL . 'assets/js/find-training-map.js',
|
HVAC_PLUGIN_URL . 'assets/js/find-training-map.js',
|
||||||
['jquery', 'google-maps-api', 'google-maps-markerclusterer'],
|
$map_script_deps,
|
||||||
HVAC_VERSION,
|
HVAC_VERSION,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
@ -204,27 +231,38 @@ class HVAC_Find_Training_Page {
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Get reCAPTCHA site key if available
|
||||||
|
$recaptcha_site_key = '';
|
||||||
|
if (class_exists('HVAC_Recaptcha')) {
|
||||||
|
$recaptcha_site_key = HVAC_Recaptcha::instance()->get_site_key();
|
||||||
|
}
|
||||||
|
|
||||||
// Localize script with data
|
// Localize script with data
|
||||||
wp_localize_script('hvac-find-training-map', 'hvacFindTraining', [
|
wp_localize_script('hvac-find-training-map', 'hvacFindTraining', [
|
||||||
'ajax_url' => admin_url('admin-ajax.php'),
|
'ajax_url' => admin_url('admin-ajax.php'),
|
||||||
'nonce' => wp_create_nonce('hvac_find_training'),
|
'nonce' => wp_create_nonce('hvac_find_training'),
|
||||||
'api_key' => !empty($this->api_key) ? 'configured' : '', // Don't expose actual key
|
'api_key_configured' => $api_key_configured,
|
||||||
'map_center' => [
|
'map_center' => [
|
||||||
'lat' => 39.8283, // US center
|
'lat' => 39.8283, // US center
|
||||||
'lng' => -98.5795
|
'lng' => -98.5795
|
||||||
],
|
],
|
||||||
'default_zoom' => 4,
|
'default_zoom' => 4,
|
||||||
'cluster_zoom' => 8,
|
'cluster_zoom' => 8,
|
||||||
|
'recaptcha_site_key' => $recaptcha_site_key,
|
||||||
'messages' => [
|
'messages' => [
|
||||||
'loading' => __('Loading...', 'hvac-community-events'),
|
'loading' => __('Loading...', 'hvac-community-events'),
|
||||||
'error' => __('An error occurred. Please try again.', 'hvac-community-events'),
|
'error' => __('An error occurred. Please try again.', 'hvac-community-events'),
|
||||||
'no_results' => __('No trainers or venues found matching your criteria.', 'hvac-community-events'),
|
'no_results' => __('No trainers or venues found matching your criteria.', 'hvac-community-events'),
|
||||||
'geolocation_error' => __('Unable to get your location. Please check your browser settings.', 'hvac-community-events'),
|
'geolocation_error' => __('Unable to get your location. Please check your browser settings.', 'hvac-community-events'),
|
||||||
'geolocation_unsupported' => __('Geolocation is not supported by your browser.', 'hvac-community-events')
|
'geolocation_unsupported' => __('Geolocation is not supported by your browser.', 'hvac-community-events'),
|
||||||
|
'api_key_missing' => __('Google Maps API key is not configured.', 'hvac-community-events'),
|
||||||
|
'captcha_required' => __('Please complete the CAPTCHA verification.', 'hvac-community-events'),
|
||||||
|
'captcha_failed' => __('CAPTCHA verification failed. Please try again.', 'hvac-community-events')
|
||||||
],
|
],
|
||||||
'marker_icons' => [
|
'marker_icons' => [
|
||||||
'trainer' => HVAC_PLUGIN_URL . 'assets/images/marker-trainer.svg',
|
'trainer' => HVAC_PLUGIN_URL . 'assets/images/marker-trainer.svg',
|
||||||
'venue' => HVAC_PLUGIN_URL . 'assets/images/marker-venue.svg'
|
'venue' => HVAC_PLUGIN_URL . 'assets/images/marker-venue.svg',
|
||||||
|
'event' => HVAC_PLUGIN_URL . 'assets/images/marker-event.svg'
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
@ -255,7 +293,7 @@ class HVAC_Find_Training_Page {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AJAX: Get all map data (trainers and venues)
|
* AJAX: Get all map data (trainers, venues, and events)
|
||||||
*/
|
*/
|
||||||
public function ajax_get_map_data(): void {
|
public function ajax_get_map_data(): void {
|
||||||
// Verify nonce
|
// Verify nonce
|
||||||
|
|
@ -268,12 +306,15 @@ class HVAC_Find_Training_Page {
|
||||||
|
|
||||||
$trainers = $data_provider->get_trainer_markers();
|
$trainers = $data_provider->get_trainer_markers();
|
||||||
$venues = $data_provider->get_venue_markers();
|
$venues = $data_provider->get_venue_markers();
|
||||||
|
$events = $data_provider->get_event_markers();
|
||||||
|
|
||||||
wp_send_json_success([
|
wp_send_json_success([
|
||||||
'trainers' => $trainers,
|
'trainers' => $trainers,
|
||||||
'venues' => $venues,
|
'venues' => $venues,
|
||||||
|
'events' => $events,
|
||||||
'total_trainers' => count($trainers),
|
'total_trainers' => count($trainers),
|
||||||
'total_venues' => count($venues)
|
'total_venues' => count($venues),
|
||||||
|
'total_events' => count($events)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -294,6 +335,8 @@ class HVAC_Find_Training_Page {
|
||||||
'search' => sanitize_text_field($_POST['search'] ?? ''),
|
'search' => sanitize_text_field($_POST['search'] ?? ''),
|
||||||
'show_trainers' => filter_var($_POST['show_trainers'] ?? true, FILTER_VALIDATE_BOOLEAN),
|
'show_trainers' => filter_var($_POST['show_trainers'] ?? true, FILTER_VALIDATE_BOOLEAN),
|
||||||
'show_venues' => filter_var($_POST['show_venues'] ?? true, FILTER_VALIDATE_BOOLEAN),
|
'show_venues' => filter_var($_POST['show_venues'] ?? true, FILTER_VALIDATE_BOOLEAN),
|
||||||
|
'show_events' => filter_var($_POST['show_events'] ?? true, FILTER_VALIDATE_BOOLEAN),
|
||||||
|
'include_past' => filter_var($_POST['include_past'] ?? false, FILTER_VALIDATE_BOOLEAN),
|
||||||
'lat' => isset($_POST['lat']) ? floatval($_POST['lat']) : null,
|
'lat' => isset($_POST['lat']) ? floatval($_POST['lat']) : null,
|
||||||
'lng' => isset($_POST['lng']) ? floatval($_POST['lng']) : null,
|
'lng' => isset($_POST['lng']) ? floatval($_POST['lng']) : null,
|
||||||
'radius' => isset($_POST['radius']) ? intval($_POST['radius']) : 100 // km
|
'radius' => isset($_POST['radius']) ? intval($_POST['radius']) : 100 // km
|
||||||
|
|
@ -303,7 +346,8 @@ class HVAC_Find_Training_Page {
|
||||||
|
|
||||||
$result = [
|
$result = [
|
||||||
'trainers' => [],
|
'trainers' => [],
|
||||||
'venues' => []
|
'venues' => [],
|
||||||
|
'events' => []
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($filters['show_trainers']) {
|
if ($filters['show_trainers']) {
|
||||||
|
|
@ -314,8 +358,13 @@ class HVAC_Find_Training_Page {
|
||||||
$result['venues'] = $data_provider->get_venue_markers($filters);
|
$result['venues'] = $data_provider->get_venue_markers($filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($filters['show_events']) {
|
||||||
|
$result['events'] = $data_provider->get_event_markers($filters);
|
||||||
|
}
|
||||||
|
|
||||||
$result['total_trainers'] = count($result['trainers']);
|
$result['total_trainers'] = count($result['trainers']);
|
||||||
$result['total_venues'] = count($result['venues']);
|
$result['total_venues'] = count($result['venues']);
|
||||||
|
$result['total_events'] = count($result['events']);
|
||||||
$result['filters_applied'] = array_filter($filters, function($v) {
|
$result['filters_applied'] = array_filter($filters, function($v) {
|
||||||
return !empty($v) && $v !== true;
|
return !empty($v) && $v !== true;
|
||||||
});
|
});
|
||||||
|
|
@ -477,6 +526,11 @@ class HVAC_Find_Training_Page {
|
||||||
<div class="hvac-form-field">
|
<div class="hvac-form-field">
|
||||||
<textarea name="message" placeholder="Message" rows="3"></textarea>
|
<textarea name="message" placeholder="Message" rows="3"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="hvac-form-field hvac-recaptcha-wrapper">
|
||||||
|
<?php if (class_exists('HVAC_Recaptcha')): ?>
|
||||||
|
<?php HVAC_Recaptcha::instance()->echo_widget('trainer-contact'); ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
<button type="submit" class="hvac-btn-primary">Send Message</button>
|
<button type="submit" class="hvac-btn-primary">Send Message</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,12 +69,28 @@ class HVAC_Venue_Geocoding {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load API key from secure storage
|
* Load API key from secure storage or plain option
|
||||||
|
* Uses dedicated geocoding key if available, falls back to maps key
|
||||||
*/
|
*/
|
||||||
private function load_api_key(): void {
|
private function load_api_key(): void {
|
||||||
|
// First try dedicated geocoding API key (IP-restricted for server-side use)
|
||||||
|
// Check plain option first (simpler setup)
|
||||||
|
$this->api_key = get_option('hvac_google_geocoding_api_key', '');
|
||||||
|
|
||||||
|
// Try secure storage if plain option not set
|
||||||
|
if (empty($this->api_key) && class_exists('HVAC_Secure_Storage')) {
|
||||||
|
$this->api_key = HVAC_Secure_Storage::get_credential('hvac_google_geocoding_api_key', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to maps API key if geocoding key not set
|
||||||
|
if (empty($this->api_key)) {
|
||||||
if (class_exists('HVAC_Secure_Storage')) {
|
if (class_exists('HVAC_Secure_Storage')) {
|
||||||
$this->api_key = HVAC_Secure_Storage::get_credential('hvac_google_maps_api_key', '');
|
$this->api_key = HVAC_Secure_Storage::get_credential('hvac_google_maps_api_key', '');
|
||||||
}
|
}
|
||||||
|
if (empty($this->api_key)) {
|
||||||
|
$this->api_key = get_option('hvac_google_maps_api_key', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -90,6 +106,12 @@ class HVAC_Venue_Geocoding {
|
||||||
// Admin action for batch geocoding
|
// Admin action for batch geocoding
|
||||||
add_action('wp_ajax_hvac_batch_geocode_venues', [$this, 'ajax_batch_geocode']);
|
add_action('wp_ajax_hvac_batch_geocode_venues', [$this, 'ajax_batch_geocode']);
|
||||||
|
|
||||||
|
// Admin action for marking venues as approved labs (legacy)
|
||||||
|
add_action('wp_ajax_hvac_mark_venues_approved', [$this, 'ajax_mark_venues_approved']);
|
||||||
|
|
||||||
|
// Admin action for updating approved labs list
|
||||||
|
add_action('wp_ajax_hvac_update_approved_labs', [$this, 'ajax_update_approved_labs']);
|
||||||
|
|
||||||
// Clear venue coordinates when address changes
|
// Clear venue coordinates when address changes
|
||||||
add_action('updated_post_meta', [$this, 'on_venue_meta_update'], 10, 4);
|
add_action('updated_post_meta', [$this, 'on_venue_meta_update'], 10, 4);
|
||||||
}
|
}
|
||||||
|
|
@ -412,6 +434,158 @@ class HVAC_Venue_Geocoding {
|
||||||
wp_send_json_success($result);
|
wp_send_json_success($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AJAX handler for marking geocoded venues as approved training labs
|
||||||
|
*/
|
||||||
|
public function ajax_mark_venues_approved(): void {
|
||||||
|
// Check permissions
|
||||||
|
if (!current_user_can('manage_options')) {
|
||||||
|
wp_send_json_error(['message' => 'Permission denied']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify nonce
|
||||||
|
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_mark_venues_approved')) {
|
||||||
|
wp_send_json_error(['message' => 'Invalid security token']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->mark_geocoded_venues_as_approved();
|
||||||
|
|
||||||
|
wp_send_json_success($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark all geocoded venues as approved training labs
|
||||||
|
*
|
||||||
|
* @return array Results with count of venues marked
|
||||||
|
*/
|
||||||
|
public function mark_geocoded_venues_as_approved(): array {
|
||||||
|
// Get all venues that have coordinates but are NOT already approved labs
|
||||||
|
$venues = get_posts([
|
||||||
|
'post_type' => 'tribe_venue',
|
||||||
|
'posts_per_page' => -1,
|
||||||
|
'post_status' => 'publish',
|
||||||
|
'meta_query' => [
|
||||||
|
'relation' => 'OR',
|
||||||
|
[
|
||||||
|
'key' => 'venue_latitude',
|
||||||
|
'compare' => 'EXISTS'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => '_VenueLat',
|
||||||
|
'compare' => 'EXISTS'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'tax_query' => [
|
||||||
|
[
|
||||||
|
'taxonomy' => 'venue_type',
|
||||||
|
'field' => 'slug',
|
||||||
|
'terms' => 'mq-approved-lab',
|
||||||
|
'operator' => 'NOT IN'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$results = [
|
||||||
|
'marked' => 0,
|
||||||
|
'failed' => 0,
|
||||||
|
'total_approved' => 0
|
||||||
|
];
|
||||||
|
|
||||||
|
// Load venue categories class
|
||||||
|
if (!class_exists('HVAC_Venue_Categories')) {
|
||||||
|
require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-venue-categories.php';
|
||||||
|
}
|
||||||
|
$venue_categories = HVAC_Venue_Categories::instance();
|
||||||
|
|
||||||
|
foreach ($venues as $venue) {
|
||||||
|
$result = $venue_categories->set_as_approved_lab($venue->ID);
|
||||||
|
if (is_wp_error($result)) {
|
||||||
|
$results['failed']++;
|
||||||
|
} else {
|
||||||
|
$results['marked']++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count total approved labs
|
||||||
|
$approved_query = new WP_Query([
|
||||||
|
'post_type' => 'tribe_venue',
|
||||||
|
'posts_per_page' => 1,
|
||||||
|
'post_status' => 'publish',
|
||||||
|
'fields' => 'ids',
|
||||||
|
'tax_query' => [
|
||||||
|
[
|
||||||
|
'taxonomy' => 'venue_type',
|
||||||
|
'field' => 'slug',
|
||||||
|
'terms' => 'mq-approved-lab',
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$results['total_approved'] = $approved_query->found_posts;
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AJAX handler for updating approved labs list with specific venue IDs
|
||||||
|
*/
|
||||||
|
public function ajax_update_approved_labs(): void {
|
||||||
|
// Check permissions
|
||||||
|
if (!current_user_can('manage_options')) {
|
||||||
|
wp_send_json_error(['message' => 'Permission denied']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify nonce
|
||||||
|
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_mark_venues_approved')) {
|
||||||
|
wp_send_json_error(['message' => 'Invalid security token']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get venue IDs from request (these are the ones that should be approved)
|
||||||
|
$selected_ids = isset($_POST['venue_ids']) ? array_map('absint', (array)$_POST['venue_ids']) : [];
|
||||||
|
|
||||||
|
// Load venue categories class
|
||||||
|
if (!class_exists('HVAC_Venue_Categories')) {
|
||||||
|
require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-venue-categories.php';
|
||||||
|
}
|
||||||
|
$venue_categories = HVAC_Venue_Categories::instance();
|
||||||
|
|
||||||
|
// Get all venues
|
||||||
|
$all_venues = get_posts([
|
||||||
|
'post_type' => 'tribe_venue',
|
||||||
|
'posts_per_page' => -1,
|
||||||
|
'post_status' => 'publish',
|
||||||
|
'fields' => 'ids'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$added = 0;
|
||||||
|
$removed = 0;
|
||||||
|
|
||||||
|
foreach ($all_venues as $venue_id) {
|
||||||
|
$is_currently_approved = has_term('mq-approved-lab', 'venue_type', $venue_id);
|
||||||
|
$should_be_approved = in_array($venue_id, $selected_ids);
|
||||||
|
|
||||||
|
if ($should_be_approved && !$is_currently_approved) {
|
||||||
|
// Add the term
|
||||||
|
wp_set_post_terms($venue_id, ['mq-approved-lab'], 'venue_type', false);
|
||||||
|
$added++;
|
||||||
|
} elseif (!$should_be_approved && $is_currently_approved) {
|
||||||
|
// Remove the term
|
||||||
|
wp_remove_object_terms($venue_id, 'mq-approved-lab', 'venue_type');
|
||||||
|
$removed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_send_json_success([
|
||||||
|
'approved_count' => count($selected_ids),
|
||||||
|
'added' => $added,
|
||||||
|
'removed' => $removed
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Batch geocode venues without coordinates
|
* Batch geocode venues without coordinates
|
||||||
*
|
*
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue